Merge branch 'feature/basket-1630'
This commit is contained in:
commit
5295165386
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Clicommands;
|
||||
|
||||
use Icinga\Date\DateFormatter;
|
||||
use Icinga\Module\Director\Cli\Command;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\Basket;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshot;
|
||||
|
||||
/**
|
||||
* Export Director Config Objects
|
||||
*/
|
||||
class BasketCommand extends Command
|
||||
{
|
||||
/**
|
||||
* List configured Baskets
|
||||
*
|
||||
* USAGE
|
||||
*
|
||||
* icingacli director basket list
|
||||
*
|
||||
* OPTIONS
|
||||
*/
|
||||
public function listAction()
|
||||
{
|
||||
$db = $this->db()->getDbAdapter();
|
||||
$query = $db->select()
|
||||
->from('director_basket', 'basket_name')
|
||||
->order('basket_name');
|
||||
foreach ($db->fetchCol($query) as $name) {
|
||||
echo "$name\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON-dump for objects related to the given Basket
|
||||
*
|
||||
* USAGE
|
||||
*
|
||||
* icingacli director basket dump --name <basket>
|
||||
*
|
||||
* OPTIONS
|
||||
*/
|
||||
public function dumpAction()
|
||||
{
|
||||
$basket = $this->requireBasket();
|
||||
$snapshot = BasketSnapshot::createForBasket($basket, $this->db());
|
||||
echo $snapshot->getJsonDump() . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a snapshot for the given Basket
|
||||
*
|
||||
* USAGE
|
||||
*
|
||||
* icingacli director basket snapshot --name <basket>
|
||||
*
|
||||
* OPTIONS
|
||||
*/
|
||||
public function snapshotAction()
|
||||
{
|
||||
$basket = $this->requireBasket();
|
||||
$snapshot = BasketSnapshot::createForBasket($basket, $this->db());
|
||||
$snapshot->store();
|
||||
$hexSum = bin2hex($snapshot->get('content_checksum'));
|
||||
printf(
|
||||
"Snapshot '%s' taken for Basket '%s' at %s\n",
|
||||
substr($hexSum, 0, 7),
|
||||
$basket->get('basket_name'),
|
||||
DateFormatter::formatDateTime($snapshot->get('ts_create') / 1000)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a Basket from JSON dump provided on STDIN
|
||||
*
|
||||
* USAGE
|
||||
*
|
||||
* icingacli director basket restore < basket-dump.json
|
||||
*
|
||||
* OPTIONS
|
||||
*/
|
||||
public function restoreAction()
|
||||
{
|
||||
$json = file_get_contents('php://stdin');
|
||||
BasketSnapshot::restoreJson($json, $this->db());
|
||||
echo "Objects from Basket Snapshot have been restored\n";
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
protected function requireBasket()
|
||||
{
|
||||
return Basket::load($this->params->getRequired('name'), $this->db());
|
||||
}
|
||||
}
|
|
@ -125,7 +125,10 @@ class ImportsourceCommand extends Command
|
|||
*/
|
||||
protected function getImportSource()
|
||||
{
|
||||
return ImportSource::load($this->params->getRequired('id'), $this->db());
|
||||
return ImportSource::loadWithAutoIncId(
|
||||
(int) $this->params->getRequired('id'),
|
||||
$this->db()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -95,7 +95,10 @@ class SyncruleCommand extends Command
|
|||
*/
|
||||
protected function getSyncRule()
|
||||
{
|
||||
return SyncRule::load($this->params->getRequired('id'), $this->db());
|
||||
return SyncRule::loadWithAutoIncId(
|
||||
(int) $this->params->getRequired('id'),
|
||||
$this->db()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,347 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Controllers;
|
||||
|
||||
use dipl\Html\Link;
|
||||
use dipl\Web\Widget\NameValueTable;
|
||||
use Exception;
|
||||
use Icinga\Date\DateFormatter;
|
||||
use Icinga\Module\Director\ConfigDiff;
|
||||
use Icinga\Module\Director\Core\Json;
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\Basket;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshot;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshotFieldResolver;
|
||||
use Icinga\Module\Director\Forms\AddToBasketForm;
|
||||
use Icinga\Module\Director\Forms\BasketCreateSnapshotForm;
|
||||
use Icinga\Module\Director\Forms\BasketForm;
|
||||
use Icinga\Module\Director\Forms\RestoreBasketForm;
|
||||
use Icinga\Module\Director\Web\Controller\ActionController;
|
||||
use dipl\Html\Html;
|
||||
use Icinga\Module\Director\Web\Table\BasketSnapshotTable;
|
||||
|
||||
class BasketController extends ActionController
|
||||
{
|
||||
protected $isApified = true;
|
||||
|
||||
protected function basketTabs()
|
||||
{
|
||||
$name = $this->params->get('name');
|
||||
return $this->tabs()->add('show', [
|
||||
'label' => $this->translate('Basket'),
|
||||
'url' => 'director/basket',
|
||||
'urlParams' => ['name' => $name]
|
||||
])->add('snapshots', [
|
||||
'label' => $this->translate('Snapshots'),
|
||||
'url' => 'director/basket/snapshots',
|
||||
'urlParams' => ['name' => $name]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
* @throws \Icinga\Exception\MissingParameterException
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
$this->actions()->add(
|
||||
Link::create(
|
||||
$this->translate('Back'),
|
||||
'director/baskets',
|
||||
null,
|
||||
['class' => 'icon-left-big']
|
||||
)
|
||||
);
|
||||
$basket = $this->requireBasket();
|
||||
$this->basketTabs()->activate('show');
|
||||
$this->addTitle($basket->get('basket_name'));
|
||||
if ($basket->isEmpty()) {
|
||||
$this->content()->add(Html::tag('p', [
|
||||
'class' => 'information'
|
||||
], $this->translate('This basket is empty')));
|
||||
}
|
||||
$this->content()->add(
|
||||
(new BasketForm())->setObject($basket)->handleRequest()
|
||||
);
|
||||
}
|
||||
|
||||
public function addAction()
|
||||
{
|
||||
$this->addSingleTab($this->translate('Add to Basket'));
|
||||
$this->addTitle($this->translate('Add chosen objects to a Configuration Basket'));
|
||||
$form = new AddToBasketForm();
|
||||
$form->setDb($this->db())
|
||||
->setType($this->params->getRequired('type'))
|
||||
->setNames($this->url()->getParams()->getValues('names'))
|
||||
->handleRequest();
|
||||
$this->content()->add($form);
|
||||
}
|
||||
|
||||
public function createAction()
|
||||
{
|
||||
$this->actions()->add(
|
||||
Link::create(
|
||||
$this->translate('back'),
|
||||
'director/baskets',
|
||||
null,
|
||||
['class' => 'icon-left-big']
|
||||
)
|
||||
);
|
||||
$this->addSingleTab($this->translate('Create Basket'));
|
||||
$this->addTitle($this->translate('Create a new Configuration Basket'));
|
||||
$form = (new BasketForm())
|
||||
->setDb($this->db())
|
||||
->handleRequest();
|
||||
$this->content()->add($form);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function snapshotsAction()
|
||||
{
|
||||
$name = $this->params->get('name');
|
||||
if ($name === null || $name === '') {
|
||||
$basket = null;
|
||||
} else {
|
||||
$basket = Basket::load($name, $this->db());
|
||||
}
|
||||
if ($basket === null) {
|
||||
$this->addTitle($this->translate('Basket Snapshots'));
|
||||
$this->addSingleTab($this->translate('Snapshots'));
|
||||
} else {
|
||||
$this->addTitle(sprintf(
|
||||
$this->translate('%: Snapshots'),
|
||||
$basket->get('basket_name')
|
||||
));
|
||||
$this->basketTabs()->activate('snapshots');
|
||||
}
|
||||
if ($basket !== null) {
|
||||
$this->content()->add(
|
||||
(new BasketCreateSnapshotForm())
|
||||
->setBasket($basket)
|
||||
->handleRequest()
|
||||
);
|
||||
}
|
||||
$table = new BasketSnapshotTable($this->db());
|
||||
if ($basket !== null) {
|
||||
$table->setBasket($basket);
|
||||
}
|
||||
|
||||
$table->renderTo($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\MissingParameterException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function snapshotAction()
|
||||
{
|
||||
$basket = $this->requireBasket();
|
||||
$snapshot = BasketSnapshot::load([
|
||||
'basket_uuid' => $basket->get('uuid'),
|
||||
'ts_create' => $this->params->getRequired('ts'),
|
||||
], $this->db());
|
||||
$snapSum = bin2hex($snapshot->get('content_checksum'));
|
||||
|
||||
if ($this->params->get('action') === 'download') {
|
||||
$this->getResponse()->setHeader('Content-Type', 'application/json', true);
|
||||
echo $snapshot->getJsonDump();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->addTitle(
|
||||
$this->translate('%s: %s (Snapshot)'),
|
||||
$basket->get('basket_name'),
|
||||
substr($snapSum, 0, 7)
|
||||
);
|
||||
|
||||
$this->actions()->add([
|
||||
Link::create(
|
||||
$this->translate('Show Basket'),
|
||||
'director/basket',
|
||||
['name' => $basket->get('basket_name')],
|
||||
['data-base-target' => '_next']
|
||||
),
|
||||
Link::create(
|
||||
$this->translate('Restore'),
|
||||
$this->url()->with('action', 'restore'),
|
||||
null,
|
||||
['class' => 'icon-rewind']
|
||||
),
|
||||
Link::create(
|
||||
$this->translate('Download'),
|
||||
$this->url()->with('action', 'download'),
|
||||
null,
|
||||
[
|
||||
'class' => 'icon-download',
|
||||
'target' => '_blank'
|
||||
]
|
||||
),
|
||||
]);
|
||||
|
||||
$properties = new NameValueTable();
|
||||
$properties->addNameValuePairs([
|
||||
$this->translate('Created') => DateFormatter::formatDateTime($snapshot->get('ts_create') / 1000),
|
||||
$this->translate('Content Checksum') => bin2hex($snapshot->get('content_checksum')),
|
||||
]);
|
||||
$this->content()->add($properties);
|
||||
|
||||
if ($this->params->get('action') === 'restore') {
|
||||
$form = new RestoreBasketForm();
|
||||
$form
|
||||
->setSnapshot($snapshot)
|
||||
->handleRequest();
|
||||
$this->content()->add($form);
|
||||
$targetDbName = $form->getValue('target_db');
|
||||
$connection = $form->getDb();
|
||||
} else {
|
||||
$targetDbName = null;
|
||||
$connection = $this->db();
|
||||
}
|
||||
|
||||
$json = $snapshot->getJsonDump();
|
||||
$this->addSingleTab($this->translate('Snapshot'));
|
||||
$all = Json::decode($json);
|
||||
$fieldResolver = new BasketSnapshotFieldResolver($all, $connection);
|
||||
foreach ($all as $type => $objects) {
|
||||
if ($type === 'Datafield') {
|
||||
// TODO: we should now be able to show all fields and link
|
||||
// to a "diff" for the ones that should be created
|
||||
// $this->content()->add(Html::tag('h2', sprintf('+%d Datafield(s)', count($objects))));
|
||||
continue;
|
||||
}
|
||||
$table = new NameValueTable();
|
||||
$table->setAttribute('data-base-target', '_next');
|
||||
foreach ($objects as $key => $object) {
|
||||
$linkParams = [
|
||||
'name' => $basket->get('basket_name'),
|
||||
'checksum' => $this->params->get('checksum'),
|
||||
'ts' => $this->params->get('ts'),
|
||||
'type' => $type,
|
||||
'key' => $key,
|
||||
];
|
||||
if ($targetDbName !== null) {
|
||||
$linkParams['target_db'] = $targetDbName;
|
||||
}
|
||||
try {
|
||||
$current = BasketSnapshot::instanceByIdentifier($type, $key, $connection);
|
||||
if ($current === null) {
|
||||
$table->addNameValueRow(
|
||||
$key,
|
||||
Link::create(
|
||||
Html::tag('strong', ['style' => 'color: green'], $this->translate('new')),
|
||||
'director/basket/snapshotobject',
|
||||
$linkParams
|
||||
)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
$currentExport = $current->export();
|
||||
$fieldResolver->tweakTargetIds($currentExport);
|
||||
$hasChanged = Json::encode($currentExport) !== Json::encode($object);
|
||||
$table->addNameValueRow(
|
||||
$key,
|
||||
$hasChanged
|
||||
? Link::create(
|
||||
Html::tag('strong', ['style' => 'color: orange'], $this->translate('modified')),
|
||||
'director/basket/snapshotobject',
|
||||
$linkParams
|
||||
)
|
||||
: Html::tag('span', ['style' => 'color: green'], $this->translate('unchanged'))
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$table->addNameValueRow(
|
||||
$key,
|
||||
$e->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
$this->content()->add(Html::tag('h2', $type));
|
||||
$this->content()->add($table);
|
||||
}
|
||||
$this->content()->add(Html::tag('div', ['style' => 'height: 5em']));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\MissingParameterException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function snapshotobjectAction()
|
||||
{
|
||||
$basket = $this->requireBasket();
|
||||
$snapshot = BasketSnapshot::load([
|
||||
'basket_uuid' => $basket->get('uuid'),
|
||||
'ts_create' => $this->params->getRequired('ts'),
|
||||
], $this->db());
|
||||
$snapshotUrl = $this->url()->without('type')->without('key')->setPath('director/basket/snapshot');
|
||||
$type = $this->params->get('type');
|
||||
$key = $this->params->get('key');
|
||||
|
||||
$this->addTitle($this->translate('Single Object Diff'));
|
||||
$this->content()->add(Html::tag('p', [
|
||||
'class' => 'information'
|
||||
], Html::sprintf(
|
||||
$this->translate('Comparing %s "%s" from Snapshot "%s" to current config'),
|
||||
$type,
|
||||
$key,
|
||||
Link::create(
|
||||
substr(bin2hex($snapshot->get('content_checksum')), 0, 7),
|
||||
$snapshotUrl,
|
||||
null,
|
||||
['data-base-target' => '_next']
|
||||
)
|
||||
)));
|
||||
$this->actions()->add([
|
||||
Link::create(
|
||||
$this->translate('back'),
|
||||
$snapshotUrl,
|
||||
null,
|
||||
['class' => 'icon-left-big']
|
||||
),
|
||||
Link::create(
|
||||
$this->translate('Restore'),
|
||||
$this->url()->with('action', 'restore'),
|
||||
null,
|
||||
['class' => 'icon-rewind']
|
||||
)
|
||||
]);
|
||||
|
||||
$json = $snapshot->getJsonDump();
|
||||
$this->addSingleTab($this->translate('Snapshot'));
|
||||
$objects = Json::decode($json);
|
||||
$targetDbName = $this->params->get('target_db');
|
||||
if ($targetDbName === null) {
|
||||
$connection = $this->db();
|
||||
} else {
|
||||
$connection = Db::fromResourceName($targetDbName);
|
||||
}
|
||||
$fieldResolver = new BasketSnapshotFieldResolver($objects, $connection);
|
||||
$objectFromBasket = $objects->$type->$key;
|
||||
$current = BasketSnapshot::instanceByIdentifier($type, $key, $connection);
|
||||
if ($current === null) {
|
||||
$current = '';
|
||||
} else {
|
||||
$exported = $current->export();
|
||||
$fieldResolver->tweakTargetIds($exported);
|
||||
$current = Json::encode($exported, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
$this->content()->add(
|
||||
ConfigDiff::create(
|
||||
$current,
|
||||
Json::encode($objectFromBasket, JSON_PRETTY_PRINT)
|
||||
)->setHtmlRenderer('Inline')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Basket
|
||||
* @throws \Icinga\Exception\MissingParameterException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
protected function requireBasket()
|
||||
{
|
||||
return Basket::load($this->params->getRequired('name'), $this->db());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Controllers;
|
||||
|
||||
use dipl\Html\Html;
|
||||
use dipl\Html\Link;
|
||||
use Icinga\Module\Director\Web\Controller\ActionController;
|
||||
use Icinga\Module\Director\Web\Table\BasketTable;
|
||||
|
||||
class BasketsController extends ActionController
|
||||
{
|
||||
protected $isApified = true;
|
||||
|
||||
public function indexAction()
|
||||
{
|
||||
$this->setAutorefreshInterval(10);
|
||||
$this->addSingleTab($this->translate('Baskets'));
|
||||
$this->actions()->add([
|
||||
Link::create(
|
||||
$this->translate('Create'),
|
||||
'director/basket/create',
|
||||
null,
|
||||
['class' => 'icon-plus']
|
||||
)
|
||||
]);
|
||||
$this->addTitle($this->translate('Configuration Baskets'));
|
||||
$this->content()->add(Html::tag('p', $this->translate(
|
||||
'A Configuration Basket references a specific Configuration'
|
||||
. ' Objects or all objects of a specific type. It has been'
|
||||
. ' designed to share Templates, Import/Sync strategies and'
|
||||
. ' other base Configuration Objects. It is not a tool to'
|
||||
. ' operate with single Hosts or Services.'
|
||||
)));
|
||||
$this->content()->add(Html::tag('p', $this->translate(
|
||||
'You can create Basket snapshots at any time, this will persist'
|
||||
. ' a serialized representation of all involved objects at that'
|
||||
. ' moment in time. Snapshots can be exported, imported, shared'
|
||||
. ' and restored - to the very same or another Director instance.'
|
||||
)));
|
||||
$table = (new BasketTable($this->db()))
|
||||
->setAttribute('data-base-target', '_self');
|
||||
if ($table->hasSearch() || count($table)) {
|
||||
$table->renderTo($this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,7 +31,7 @@ class DashboardController extends ActionController
|
|||
$this->setAutorefreshInterval(10);
|
||||
}
|
||||
|
||||
$mainDashboards = ['Objects', 'Alerts', 'Automation', 'Deployment', 'Data'];
|
||||
$mainDashboards = ['Objects', 'Alerts', 'Automation', 'Deployment', 'Director', 'Data'];
|
||||
$this->setTitle($this->translate('Icinga Director - Main Dashboard'));
|
||||
$names = $this->params->getValues('name', $mainDashboards);
|
||||
if (! $this->params->has('name')) {
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
|
||||
namespace Icinga\Module\Director\Controllers;
|
||||
|
||||
use dipl\Web\Url;
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Data\Filter\FilterChain;
|
||||
use Icinga\Data\Filter\FilterExpression;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
|
||||
use Icinga\Module\Director\Forms\IcingaAddServiceForm;
|
||||
use Icinga\Module\Director\Forms\IcingaAddServiceSetForm;
|
||||
use Icinga\Module\Director\Objects\IcingaHost;
|
||||
|
@ -47,6 +49,31 @@ class HostsController extends ObjectsController
|
|||
));
|
||||
}
|
||||
|
||||
public function edittemplatesAction()
|
||||
{
|
||||
parent::editAction();
|
||||
|
||||
$objects = $this->loadMultiObjectsFromParams();
|
||||
$names = [];
|
||||
/** @var ExportInterface $object */
|
||||
foreach ($objects as $object) {
|
||||
$names[] = $object->getUniqueIdentifier();
|
||||
}
|
||||
|
||||
$url = Url::fromPath('director/basket/add', [
|
||||
'type' => 'HostTemplate',
|
||||
]);
|
||||
|
||||
$url->getParams()->addValues('names', $names);
|
||||
|
||||
$this->actions()->add(Link::create(
|
||||
$this->translate('Add to Basket'),
|
||||
$url,
|
||||
null,
|
||||
['class' => 'icon-tag']
|
||||
));
|
||||
}
|
||||
|
||||
public function addserviceAction()
|
||||
{
|
||||
$this->addSingleTab($this->translate('Add Service'));
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Icinga\Module\Director\Controllers;
|
||||
|
||||
use Icinga\Exception\NotFoundError;
|
||||
use Icinga\Module\Director\Forms\ImportRowModifierForm;
|
||||
use Icinga\Module\Director\Forms\ImportSourceForm;
|
||||
use Icinga\Module\Director\Web\ActionBar\AutomationObjectActionBar;
|
||||
|
@ -13,22 +14,30 @@ use Icinga\Module\Director\Web\Table\ImportsourceHookTable;
|
|||
use Icinga\Module\Director\Web\Table\PropertymodifierTable;
|
||||
use Icinga\Module\Director\Web\Tabs\ImportsourceTabs;
|
||||
use Icinga\Module\Director\Web\Widget\ImportSourceDetails;
|
||||
use InvalidArgumentException;
|
||||
use dipl\Html\Link;
|
||||
|
||||
class ImportsourceController extends ActionController
|
||||
{
|
||||
/** @var ImportSource|null */
|
||||
private $importSource;
|
||||
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\AuthenticationException
|
||||
* @throws \Icinga\Exception\Http\HttpNotFoundException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
* @throws \Icinga\Security\SecurityException
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
|
||||
$id = $this->params->get('source_id', $this->params->get('id'));
|
||||
$tabs = $this->tabs(new ImportsourceTabs($id));
|
||||
if ($id !== null && is_numeric($id)) {
|
||||
$this->id = (int) $id;
|
||||
}
|
||||
|
||||
$tabs = $this->tabs(new ImportsourceTabs($this->id));
|
||||
$action = $this->getRequest()->getActionName();
|
||||
if ($tabs->has($action)) {
|
||||
$tabs->activate($action);
|
||||
|
@ -40,18 +49,27 @@ class ImportsourceController extends ActionController
|
|||
$this->actions(new AutomationObjectActionBar(
|
||||
$this->getRequest()
|
||||
));
|
||||
$source = $this->getImportSource();
|
||||
|
||||
$this->actions()->add(Link::create(
|
||||
$this->translate('Add to Basket'),
|
||||
'director/basket/add',
|
||||
[
|
||||
'type' => 'ImportSource',
|
||||
'names' => $source->getUniqueIdentifier()
|
||||
],
|
||||
['class' => 'icon-tag']
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws \Icinga\Exception\IcingaException
|
||||
* @throws \Icinga\Exception\MissingParameterException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
$this->addMainActions();
|
||||
$source = ImportSource::load($this->params->getRequired('id'), $this->db());
|
||||
$source = $this->getImportSource();
|
||||
if ($this->params->get('format') === 'json') {
|
||||
$this->sendJson($this->getResponse(), $source->export());
|
||||
return;
|
||||
|
@ -63,9 +81,6 @@ class ImportsourceController extends ActionController
|
|||
$this->content()->add(new ImportSourceDetails($source));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
*/
|
||||
public function addAction()
|
||||
{
|
||||
$this->addTitle($this->translate('Add import source'))
|
||||
|
@ -77,16 +92,14 @@ class ImportsourceController extends ActionController
|
|||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws \Icinga\Exception\MissingParameterException
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
public function editAction()
|
||||
{
|
||||
$this->addMainActions();
|
||||
$this->tabs()->activateMainWithPostfix($this->translate('Modify'));
|
||||
$id = $this->params->getRequired('id');
|
||||
$form = ImportSourceForm::load()->setDb($this->db())
|
||||
->loadObject($id)
|
||||
$this->activateTabWithPostfix($this->translate('Modify'));
|
||||
$form = ImportSourceForm::load()
|
||||
->setObject($this->getImportSource())
|
||||
->setListUrl('director/importsources')
|
||||
->handleRequest();
|
||||
$this->addTitle(
|
||||
|
@ -98,16 +111,13 @@ class ImportsourceController extends ActionController
|
|||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws \Icinga\Exception\MissingParameterException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function cloneAction()
|
||||
{
|
||||
$this->addMainActions();
|
||||
$this->tabs()->activateMainWithPostfix($this->translate('Clone'));
|
||||
$id = $this->params->getRequired('id');
|
||||
$source = ImportSource::load($id, $this->db());
|
||||
$this->activateTabWithPostfix($this->translate('Clone'));
|
||||
$source = $this->getImportSource();
|
||||
$this->addTitle('Clone: %s', $source->get('source_name'));
|
||||
$form = new CloneImportSourceForm($source);
|
||||
$this->content()->add($form);
|
||||
|
@ -115,13 +125,11 @@ class ImportsourceController extends ActionController
|
|||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws \Icinga\Exception\MissingParameterException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function previewAction()
|
||||
{
|
||||
$source = ImportSource::load($this->params->getRequired('id'), $this->db());
|
||||
$source = $this->getImportSource();
|
||||
|
||||
$this->addTitle(
|
||||
$this->translate('Import source preview: %s'),
|
||||
|
@ -136,13 +144,11 @@ class ImportsourceController extends ActionController
|
|||
|
||||
/**
|
||||
* @return ImportSource
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws \Icinga\Exception\MissingParameterException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
protected function requireImportSourceAndAddModifierTable()
|
||||
{
|
||||
$source = ImportSource::load($this->params->getRequired('source_id'), $this->db());
|
||||
$source = $this->getImportSource();
|
||||
PropertymodifierTable::load($source, $this->url())
|
||||
->handleSortPriorityActions($this->getRequest(), $this->getResponse())
|
||||
->renderTo($this);
|
||||
|
@ -151,8 +157,6 @@ class ImportsourceController extends ActionController
|
|||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws \Icinga\Exception\MissingParameterException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function modifierAction()
|
||||
|
@ -162,19 +166,17 @@ class ImportsourceController extends ActionController
|
|||
$this->addAddLink(
|
||||
$this->translate('Add property modifier'),
|
||||
'director/importsource/addmodifier',
|
||||
['source_id' => $source->getId()],
|
||||
['source_id' => $source->get('id')],
|
||||
'_self'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws \Icinga\Exception\MissingParameterException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function historyAction()
|
||||
{
|
||||
$source = ImportSource::load($this->params->getRequired('id'), $this->db());
|
||||
$source = $this->getImportSource();
|
||||
$this->addTitle($this->translate('Import run history: %s'), $source->get('source_name'));
|
||||
|
||||
// TODO: temporarily disabled, find a better place for stats:
|
||||
|
@ -183,9 +185,6 @@ class ImportsourceController extends ActionController
|
|||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws \Icinga\Exception\Http\HttpNotFoundException
|
||||
* @throws \Icinga\Exception\MissingParameterException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function addmodifierAction()
|
||||
|
@ -202,14 +201,12 @@ class ImportsourceController extends ActionController
|
|||
->setSource($source)
|
||||
->setSuccessUrl(
|
||||
'director/importsource/modifier',
|
||||
['source_id' => $source->getId()]
|
||||
['source_id' => $source->get('id')]
|
||||
)->handleRequest()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws \Icinga\Exception\Http\HttpNotFoundException
|
||||
* @throws \Icinga\Exception\MissingParameterException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
|
@ -217,7 +214,7 @@ class ImportsourceController extends ActionController
|
|||
{
|
||||
// We need to load the table AFTER adding the title, otherwise search
|
||||
// will not be placed next to the title
|
||||
$source = ImportSource::load($this->params->getRequired('source_id'), $this->db());
|
||||
$source = $this->getImportSource();
|
||||
|
||||
$this->addTitle(
|
||||
$this->translate('%s: Property Modifier'),
|
||||
|
@ -237,6 +234,34 @@ class ImportsourceController extends ActionController
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ImportSource
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
protected function getImportSource()
|
||||
{
|
||||
if ($this->importSource === null) {
|
||||
if ($this->id === null) {
|
||||
throw new InvalidArgumentException('Got no ImportSource id');
|
||||
}
|
||||
$this->importSource = ImportSource::loadWithAutoIncId(
|
||||
$this->id,
|
||||
$this->db()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->importSource;
|
||||
}
|
||||
|
||||
protected function activateTabWithPostfix($title)
|
||||
{
|
||||
/** @var ImportsourceTabs $tabs */
|
||||
$tabs = $this->tabs();
|
||||
$tabs->activateMainWithPostfix($title);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportSource $source
|
||||
* @return $this
|
||||
|
@ -247,7 +272,7 @@ class ImportsourceController extends ActionController
|
|||
Link::create(
|
||||
$this->translate('back'),
|
||||
'director/importsource/modifier',
|
||||
['source_id' => $source->getId()],
|
||||
['source_id' => $source->get('id')],
|
||||
['class' => 'icon-left-big']
|
||||
)
|
||||
);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Icinga\Module\Director\Controllers;
|
||||
|
||||
use dipl\Html\Link;
|
||||
use Icinga\Module\Director\Forms\DirectorJobForm;
|
||||
use Icinga\Module\Director\Web\Controller\ActionController;
|
||||
use Icinga\Module\Director\Objects\DirectorJob;
|
||||
|
@ -19,6 +20,7 @@ class JobController extends ActionController
|
|||
$this
|
||||
->addJobTabs($job, 'show')
|
||||
->addTitle($this->translate('Job: %s'), $job->get('job_name'))
|
||||
->addToBasketLink()
|
||||
->content()->add(new JobDetails($job));
|
||||
}
|
||||
|
||||
|
@ -45,12 +47,12 @@ class JobController extends ActionController
|
|||
$form = DirectorJobForm::load()
|
||||
->setListUrl('director/jobs')
|
||||
->setObject($job)
|
||||
->loadObject($this->params->getRequired('id'))
|
||||
->handleRequest();
|
||||
|
||||
$this
|
||||
->addJobTabs($job, 'edit')
|
||||
->addTitle($this->translate('Job: %s'), $job->get('job_name'))
|
||||
->addToBasketLink()
|
||||
->content()->add($form);
|
||||
}
|
||||
|
||||
|
@ -61,7 +63,28 @@ class JobController extends ActionController
|
|||
*/
|
||||
protected function requireJob()
|
||||
{
|
||||
return DirectorJob::load($this->params->getRequired('id'), $this->db());
|
||||
return DirectorJob::loadWithAutoIncId((int) $this->params->getRequired('id'), $this->db());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
* @throws \Icinga\Exception\MissingParameterException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
protected function addToBasketLink()
|
||||
{
|
||||
$job = $this->requireJob();
|
||||
$this->actions()->add(Link::create(
|
||||
$this->translate('Add to Basket'),
|
||||
'director/basket/add',
|
||||
[
|
||||
'type' => 'DirectorJob',
|
||||
'names' => $job->getUniqueIdentifier()
|
||||
],
|
||||
['class' => 'icon-tag']
|
||||
));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function addJobTabs(DirectorJob $job, $active)
|
||||
|
|
|
@ -6,7 +6,6 @@ use Icinga\Module\Director\Forms\SyncCheckForm;
|
|||
use Icinga\Module\Director\Forms\SyncPropertyForm;
|
||||
use Icinga\Module\Director\Forms\SyncRuleForm;
|
||||
use Icinga\Module\Director\Forms\SyncRunForm;
|
||||
use Icinga\Module\Director\Objects\SyncProperty;
|
||||
use Icinga\Module\Director\Web\Controller\ActionController;
|
||||
use Icinga\Module\Director\Objects\SyncRule;
|
||||
use Icinga\Module\Director\Objects\SyncRun;
|
||||
|
@ -21,9 +20,6 @@ use dipl\Html\Link;
|
|||
class SyncruleController extends ActionController
|
||||
{
|
||||
/**
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws \Icinga\Exception\Http\HttpNotFoundException
|
||||
* @throws \Icinga\Exception\IcingaException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function indexAction()
|
||||
|
@ -49,6 +45,15 @@ class SyncruleController extends ActionController
|
|||
$this->addPropertyHint($rule);
|
||||
return;
|
||||
}
|
||||
$this->actions()->add(Link::create(
|
||||
$this->translate('Add to Basket'),
|
||||
'director/basket/add',
|
||||
[
|
||||
'type' => 'SyncRule',
|
||||
'names' => $rule->getUniqueIdentifier()
|
||||
],
|
||||
['class' => 'icon-tag']
|
||||
));
|
||||
|
||||
if (! $run) {
|
||||
$this->warning($this->translate('This Sync Rule has never been run before.'));
|
||||
|
@ -106,7 +111,6 @@ class SyncruleController extends ActionController
|
|||
|
||||
/**
|
||||
* @param SyncRule $rule
|
||||
* @throws \Icinga\Exception\IcingaException
|
||||
*/
|
||||
protected function addPropertyHint(SyncRule $rule)
|
||||
{
|
||||
|
@ -122,7 +126,6 @@ class SyncruleController extends ActionController
|
|||
|
||||
/**
|
||||
* @param $msg
|
||||
* @throws \Icinga\Exception\IcingaException
|
||||
*/
|
||||
protected function warning($msg)
|
||||
{
|
||||
|
@ -131,28 +134,17 @@ class SyncruleController extends ActionController
|
|||
|
||||
/**
|
||||
* @param $msg
|
||||
* @throws \Icinga\Exception\IcingaException
|
||||
*/
|
||||
protected function error($msg)
|
||||
{
|
||||
$this->content()->add(Html::tag('p', ['class' => 'error'], $msg));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws \Icinga\Exception\Http\HttpNotFoundException
|
||||
* @throws \Icinga\Exception\IcingaException
|
||||
*/
|
||||
public function addAction()
|
||||
{
|
||||
$this->editAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws \Icinga\Exception\Http\HttpNotFoundException
|
||||
* @throws \Icinga\Exception\IcingaException
|
||||
*/
|
||||
public function editAction()
|
||||
{
|
||||
$form = SyncRuleForm::load()
|
||||
|
@ -160,7 +152,7 @@ class SyncruleController extends ActionController
|
|||
->setDb($this->db());
|
||||
|
||||
if ($id = $this->params->get('id')) {
|
||||
$form->loadObject($id);
|
||||
$form->loadObject((int) $id);
|
||||
/** @var SyncRule $rule */
|
||||
$rule = $form->getObject();
|
||||
$this->tabs(new SyncRuleTabs($rule))->activate('edit');
|
||||
|
@ -176,6 +168,15 @@ class SyncruleController extends ActionController
|
|||
['class' => 'icon-paste']
|
||||
)
|
||||
);
|
||||
$this->actions()->add(Link::create(
|
||||
$this->translate('Add to Basket'),
|
||||
'director/basket/add',
|
||||
[
|
||||
'type' => 'SyncRule',
|
||||
'names' => $rule->getUniqueIdentifier()
|
||||
],
|
||||
['class' => 'icon-tag']
|
||||
));
|
||||
|
||||
if (! $rule->hasSyncProperties()) {
|
||||
$this->addPropertyHint($rule);
|
||||
|
@ -190,16 +191,13 @@ class SyncruleController extends ActionController
|
|||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws \Icinga\Exception\Http\HttpNotFoundException
|
||||
* @throws \Icinga\Exception\MissingParameterException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
* @throws \Icinga\Exception\ProgrammingError
|
||||
*/
|
||||
public function cloneAction()
|
||||
{
|
||||
$id = $this->params->getRequired('id');
|
||||
$rule = SyncRule::load($id, $this->db());
|
||||
$rule = SyncRule::loadWithAutoIncId((int) $id, $this->db());
|
||||
$this->tabs()->add('show', [
|
||||
'url' => 'director/syncrule',
|
||||
'urlParams' => ['id' => $id],
|
||||
|
@ -225,8 +223,7 @@ class SyncruleController extends ActionController
|
|||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\Http\HttpNotFoundException
|
||||
* @throws \Icinga\Exception\IcingaException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function propertyAction()
|
||||
{
|
||||
|
@ -247,9 +244,7 @@ class SyncruleController extends ActionController
|
|||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws \Icinga\Exception\Http\HttpNotFoundException
|
||||
* @throws \Icinga\Exception\IcingaException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function editpropertyAction()
|
||||
{
|
||||
|
@ -257,9 +252,7 @@ class SyncruleController extends ActionController
|
|||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws \Icinga\Exception\Http\HttpNotFoundException
|
||||
* @throws \Icinga\Exception\IcingaException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function addpropertyAction()
|
||||
{
|
||||
|
@ -269,7 +262,7 @@ class SyncruleController extends ActionController
|
|||
|
||||
$form = SyncPropertyForm::load()->setDb($db);
|
||||
if ($id = $this->params->get('id')) {
|
||||
$form->loadObject($id);
|
||||
$form->loadObject((int) $id);
|
||||
$this->addTitle(
|
||||
$this->translate('Sync "%s": %s'),
|
||||
$form->getObject()->get('destination_field'),
|
||||
|
@ -299,9 +292,6 @@ class SyncruleController extends ActionController
|
|||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws \Icinga\Exception\Http\HttpNotFoundException
|
||||
* @throws \Icinga\Exception\IcingaException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function historyAction()
|
||||
|
@ -321,13 +311,11 @@ class SyncruleController extends ActionController
|
|||
/**
|
||||
* @param string $key
|
||||
* @return SyncRule
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws \Icinga\Exception\IcingaException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
protected function requireSyncRule($key = 'id')
|
||||
{
|
||||
$id = $this->params->get($key);
|
||||
return SyncRule::load($id, $this->db());
|
||||
return SyncRule::loadWithAutoIncId($id, $this->db());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Forms;
|
||||
|
||||
use dipl\Html\Html;
|
||||
use dipl\Html\HtmlDocument;
|
||||
use dipl\Html\Link;
|
||||
use Icinga\Exception\NotFoundError;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\Basket;
|
||||
use Icinga\Module\Director\Web\Form\DirectorForm;
|
||||
|
||||
class AddToBasketForm extends DirectorForm
|
||||
{
|
||||
/** @var Basket */
|
||||
private $basket;
|
||||
|
||||
private $type = '(has not been set)';
|
||||
|
||||
private $names = [];
|
||||
|
||||
/**
|
||||
* @throws \Zend_Form_Exception
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function setup()
|
||||
{
|
||||
$baskets = Basket::loadAll($this->getDb());
|
||||
$enum = [];
|
||||
foreach ($baskets as $basket) {
|
||||
$enum[$basket->get('basket_name')] = $basket->get('basket_name');
|
||||
}
|
||||
|
||||
$names = [];
|
||||
$basket = null;
|
||||
if ($this->hasBeenSent()) {
|
||||
$basketName = $this->getSentValue('basket');
|
||||
if ($basketName) {
|
||||
$basket = Basket::load($basketName, $this->getDb());
|
||||
}
|
||||
}
|
||||
$count = 0;
|
||||
$type = $this->type;
|
||||
foreach ($this->names as $name) {
|
||||
if (! empty($names)) {
|
||||
$names[] = ', ';
|
||||
}
|
||||
if ($basket && $basket->hasObject($type, $name)) {
|
||||
$names[] = Html::tag('span', [
|
||||
'style' => 'text-decoration: line-through'
|
||||
], $name);
|
||||
} else {
|
||||
$count++;
|
||||
$names[] = $name;
|
||||
}
|
||||
}
|
||||
$this->addHtmlHint((new HtmlDocument())->add([
|
||||
'The following objects will be added: ',
|
||||
$names
|
||||
]));
|
||||
$this->addElement('select', 'basket', [
|
||||
'label' => $this->translate('Basket'),
|
||||
'multiOptions' => $this->optionalEnum($enum),
|
||||
'required' => true,
|
||||
'class' => 'autosubmit',
|
||||
]);
|
||||
|
||||
if ($count > 0) {
|
||||
$this->setSubmitLabel(sprintf(
|
||||
$this->translate('Add %s objects'),
|
||||
$count
|
||||
));
|
||||
} else {
|
||||
$this->setSubmitLabel($this->translate('Add'));
|
||||
$this->addSubmitButtonIfSet();
|
||||
$this->getElement($this->submitButtonName)->setAttrib('disabled', true);
|
||||
}
|
||||
}
|
||||
|
||||
public function setType($type)
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setNames($names)
|
||||
{
|
||||
$this->names = $names;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
* @throws \Icinga\Module\Director\Exception\DuplicateKeyException
|
||||
*/
|
||||
public function onSuccess()
|
||||
{
|
||||
$type = $this->type;
|
||||
$basket = Basket::load($this->getValue('basket'), $this->getDb());
|
||||
$basketName = $basket->get('basket_name');
|
||||
|
||||
if (empty($this->names)) {
|
||||
$this->getElement('basket')->addErrorMessage($this->translate(
|
||||
'No object has been chosen'
|
||||
));
|
||||
}
|
||||
if ($basket->supportsCustomSelectionFor($type)) {
|
||||
$basket->addObjects($type, $this->names);
|
||||
$basket->store();
|
||||
$this->setSuccessMessage(sprintf($this->translate(
|
||||
'Configuration objects have been added to the chosen basket "%s"'
|
||||
), $basketName));
|
||||
return parent::onSuccess();
|
||||
} else {
|
||||
$this->addHtmlHint(Html::tag('p', [
|
||||
'class' => 'error'
|
||||
], Html::sprintf($this->translate(
|
||||
'Please check your Basket configuration, %s does not support'
|
||||
. ' single "%s" configuration objects'
|
||||
), Link::create(
|
||||
$basketName,
|
||||
'director/basket',
|
||||
['name' => $basketName],
|
||||
['data-base-target' => '_next']
|
||||
), $type)));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Forms;
|
||||
|
||||
use Icinga\Module\Director\DirectorObject\Automation\Basket;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshot;
|
||||
use Icinga\Module\Director\Web\Form\DirectorForm;
|
||||
|
||||
class BasketCreateSnapshotForm extends DirectorForm
|
||||
{
|
||||
/** @var Basket */
|
||||
private $basket;
|
||||
|
||||
public function setBasket(Basket $basket)
|
||||
{
|
||||
$this->basket = $basket;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setup()
|
||||
{
|
||||
$this->setSubmitLabel($this->translate('Create Snapshot'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Module\Director\Exception\DuplicateKeyException
|
||||
*/
|
||||
public function onSuccess()
|
||||
{
|
||||
/** @var \Icinga\Module\Director\Db $connection */
|
||||
$connection = $this->basket->getConnection();
|
||||
$snapshot = BasketSnapshot::createForBasket($this->basket, $connection);
|
||||
$snapshot->store();
|
||||
parent::onSuccess();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Forms;
|
||||
|
||||
use Icinga\Module\Director\Data\Db\DbObject;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\Basket;
|
||||
use Icinga\Module\Director\Web\Form\DirectorObjectForm;
|
||||
use Zend_Form_SubForm as ZfSubForm;
|
||||
|
||||
class BasketForm extends DirectorObjectForm
|
||||
{
|
||||
protected $listUrl = 'director/baskets';
|
||||
|
||||
protected function getAvailableTypes()
|
||||
{
|
||||
return [
|
||||
'Command' => $this->translate('Command Definitions'),
|
||||
'HostGroup' => $this->translate('Host Group'),
|
||||
'IcingaTemplateChoiceHost' => $this->translate('Host Template Choice'),
|
||||
'HostTemplate' => $this->translate('Host Templates'),
|
||||
'ServiceGroup' => $this->translate('Service Groups'),
|
||||
'IcingaTemplateChoiceService' => $this->translate('Service Template Choice'),
|
||||
'ServiceTemplate' => $this->translate('Service Templates'),
|
||||
'ServiceSet' => $this->translate('Service Sets'),
|
||||
'Notification' => $this->translate('Notifications'),
|
||||
'Dependency' => $this->translate('Dependencies'),
|
||||
'ImportSource' => $this->translate('Import Sources'),
|
||||
'SyncRule' => $this->translate('Sync Rules'),
|
||||
'DirectorJob' => $this->translate('Job Definitions'),
|
||||
'Basket' => $this->translate('Basket Definitions'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Zend_Form_Exception
|
||||
*/
|
||||
public function setup()
|
||||
{
|
||||
$this->addElement('text', 'basket_name', [
|
||||
'label' => $this->translate('Basket Name'),
|
||||
'required' => true,
|
||||
]);
|
||||
|
||||
$types = $this->getAvailableTypes();
|
||||
|
||||
$options = [
|
||||
'IGNORE' => $this->translate('Ignore'),
|
||||
'ALL' => $this->translate('All of them'),
|
||||
'[]' => $this->translate('Custom Selection'),
|
||||
];
|
||||
|
||||
$this->addHtmlHint($this->translate(
|
||||
'What should we place into this Basket every time we create'
|
||||
. ' new snapshot?'
|
||||
));
|
||||
|
||||
$sub = new ZfSubForm();
|
||||
$sub->setDecorators([
|
||||
['HtmlTag', ['tag' => 'dl']],
|
||||
'FormElements'
|
||||
]);
|
||||
|
||||
foreach ($types as $name => $label) {
|
||||
$sub->addElement('select', $name, [
|
||||
'label' => $label,
|
||||
'multiOptions' => $options,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->addSubForm($sub, 'objects');
|
||||
$this->addDeleteButton();
|
||||
|
||||
$this->addHtmlHint($this->translate(
|
||||
'Choose "All" to always add all of them,'
|
||||
. ' "Ignore" to not care about a specific Type at all and'
|
||||
. ' opt for "Custom Selection" in case you want to choose'
|
||||
. ' just some specific Objects.'
|
||||
));
|
||||
}
|
||||
|
||||
protected function setDefaultsFromObject(DbObject $object)
|
||||
{
|
||||
parent::setDefaultsFromObject($object);
|
||||
/** @var Basket $object */
|
||||
$values = [];
|
||||
foreach ($this->getAvailableTypes() as $type => $label) {
|
||||
$values[$type] = 'IGNORE';
|
||||
}
|
||||
foreach ($object->getChosenObjects() as $type => $selection) {
|
||||
if ($selection === true) {
|
||||
$values[$type] = 'ALL';
|
||||
} elseif (is_array($selection)) {
|
||||
$values[$type] = '[]';
|
||||
}
|
||||
}
|
||||
|
||||
$this->populate([
|
||||
'objects' => $values
|
||||
]);
|
||||
}
|
||||
|
||||
protected function onRequest()
|
||||
{
|
||||
parent::onRequest(); // TODO: Change the autogenerated stub
|
||||
}
|
||||
|
||||
protected function getObjectClassname()
|
||||
{
|
||||
return '\\Icinga\\Module\\Director\\DirectorObject\\Automation\\Basket';
|
||||
}
|
||||
|
||||
public function onSuccess()
|
||||
{
|
||||
/** @var Basket $basket */
|
||||
$basket = $this->object();
|
||||
|
||||
if ($basket->isEmpty()) {
|
||||
$this->addError($this->translate("It's not allowed to store an empty basket"));
|
||||
|
||||
return;
|
||||
}
|
||||
if (! $basket->hasBeenLoadedFromDb()) {
|
||||
$basket->set('owner_type', 'user');
|
||||
$basket->set('owner_value', $this->getAuth()->getUser()->getUsername());
|
||||
}
|
||||
|
||||
parent::onSuccess();
|
||||
}
|
||||
|
||||
protected function setObjectSuccessUrl()
|
||||
{
|
||||
/** @var Basket $basket */
|
||||
$basket = $this->object();
|
||||
$this->setSuccessUrl(
|
||||
'director/basket',
|
||||
['name' => $basket->get('basket_name')]
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Forms;
|
||||
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Authentication\Auth;
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshot;
|
||||
use Icinga\Module\Director\Web\Controller\Extension\DirectorDb;
|
||||
use Icinga\Module\Director\Web\Form\QuickForm;
|
||||
|
||||
class RestoreBasketForm extends QuickForm
|
||||
{
|
||||
use DirectorDb;
|
||||
|
||||
/** @var BasketSnapshot */
|
||||
private $snapshot;
|
||||
|
||||
public function setSnapshot(BasketSnapshot $snapshot)
|
||||
{
|
||||
$this->snapshot = $snapshot;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @codingStandardsIgnoreStart
|
||||
* @return Auth
|
||||
*/
|
||||
protected function Auth()
|
||||
{
|
||||
return Auth::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Config
|
||||
*/
|
||||
protected function Config()
|
||||
{
|
||||
// @codingStandardsIgnoreEnd
|
||||
return Config::module('director');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Zend_Form_Exception
|
||||
*/
|
||||
public function setup()
|
||||
{
|
||||
$allowedDbs = $this->listAllowedDbResourceNames();
|
||||
if (count($allowedDbs) > 1) {
|
||||
$this->addElement('select', 'target_db', [
|
||||
'label' => $this->translate('Target DB'),
|
||||
'description' => $this->translate('Restore to this target Director DB'),
|
||||
'multiOptions' => $allowedDbs,
|
||||
'value' => $this->getRequest()->getParam('target_db', $this->getFirstDbResourceName()),
|
||||
'class' => 'autosubmit',
|
||||
]);
|
||||
}
|
||||
|
||||
$this->setSubmitLabel($this->translate('Restore'));
|
||||
}
|
||||
|
||||
public function getDb()
|
||||
{
|
||||
return Db::fromResourceName($this->getValue('target_db'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function onSuccess()
|
||||
{
|
||||
$this->snapshot->restoreTo($this->getDb());
|
||||
$this->setSuccessUrl($this->getSuccessUrl()->with('target_db', $this->getValue('target_db')));
|
||||
$this->setSuccessMessage(sprintf('Restored to %s', $this->getValue('target_db')));
|
||||
|
||||
parent::onSuccess();
|
||||
}
|
||||
}
|
|
@ -368,10 +368,11 @@ class SyncPropertyForm extends DirectorObjectForm
|
|||
{
|
||||
if ($this->importSource === null) {
|
||||
if ($this->hasObject()) {
|
||||
$this->importSource = ImportSource::load($this->object->get('source_id'), $this->db);
|
||||
$id = (int) $this->object->get('source_id');
|
||||
} else {
|
||||
$this->importSource = ImportSource::load($this->getSentValue('source_id'), $this->db);
|
||||
$id = (int) $this->getSentValue('source_id');
|
||||
}
|
||||
$this->importSource = ImportSource::loadWithAutoIncId($id, $this->db);
|
||||
}
|
||||
|
||||
return $this->importSource;
|
||||
|
|
|
@ -342,6 +342,34 @@ specific type:
|
|||
This feature is available since v1.5.0.
|
||||
|
||||
|
||||
Director Configuration Basket
|
||||
-----------------------------
|
||||
|
||||
A basket contains a set of Director Configuration objects (like Templates,
|
||||
Commands, Import/Sync definitions - but not single Hosts or Services). This
|
||||
CLI command allows you to integrate them into your very own workflows
|
||||
|
||||
## Available Actions
|
||||
|
||||
| Action | Description |
|
||||
|------------|---------------------------------------------------|
|
||||
| `dump` | JSON-dump for objects related to the given Basket |
|
||||
| `list` | List configured Baskets |
|
||||
| `restore` | Restore a Basket from JSON dump provided on STDIN |
|
||||
| `snapshot` | Take a snapshot for the given Basket |
|
||||
|
||||
### Options
|
||||
|
||||
| Option | Description |
|
||||
|----------|------------------------------------------------------|
|
||||
| `--name` | `dump` and `snapshot` require a specific object name |
|
||||
|
||||
Use `icingacli director basket restore < exported-basket.json` to restore objects
|
||||
from a specific basket. Take a snapshot or a backup first to be on the safe side.
|
||||
|
||||
This feature is available since v1.6.0.
|
||||
|
||||
|
||||
Health Check Plugin
|
||||
-------------------
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Dashboard\Dashlet;
|
||||
|
||||
class BasketDashlet extends Dashlet
|
||||
{
|
||||
protected $icon = 'tag';
|
||||
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->translate('Configuration Baskets');
|
||||
}
|
||||
|
||||
public function getSummary()
|
||||
{
|
||||
return $this->translate(
|
||||
'Preserve specific configuration objects in a specific state'
|
||||
);
|
||||
}
|
||||
|
||||
public function getUrl()
|
||||
{
|
||||
return 'director/baskets';
|
||||
}
|
||||
|
||||
public function listRequiredPermissions()
|
||||
{
|
||||
return array('director/admin');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Dashboard;
|
||||
|
||||
class DirectorDashboard extends Dashboard
|
||||
{
|
||||
protected $dashletNames = array(
|
||||
'Settings',
|
||||
'Basket',
|
||||
'SelfService',
|
||||
);
|
||||
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->translate('Icinga Director Configuration');
|
||||
}
|
||||
}
|
|
@ -8,11 +8,9 @@ class InfrastructureDashboard extends Dashboard
|
|||
{
|
||||
protected $dashletNames = array(
|
||||
'Kickstart',
|
||||
'SelfService',
|
||||
'ApiUserObject',
|
||||
'EndpointObject',
|
||||
'ZoneObject',
|
||||
'Settings',
|
||||
);
|
||||
|
||||
public function getTitle()
|
||||
|
|
|
@ -69,6 +69,9 @@ abstract class DbObject
|
|||
*/
|
||||
protected $autoincKeyName;
|
||||
|
||||
/** @var bool forbid updates to autoinc values */
|
||||
protected $protectAutoinc = true;
|
||||
|
||||
/**
|
||||
* Filled with object instances when prefetchAll is used
|
||||
*/
|
||||
|
@ -448,7 +451,11 @@ abstract class DbObject
|
|||
$props = array();
|
||||
foreach (array_keys($this->modifiedProperties) as $key) {
|
||||
if ($key === $this->autoincKeyName) {
|
||||
continue;
|
||||
if ($this->protectAutoinc) {
|
||||
continue;
|
||||
} elseif ($this->properties[$key] === null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$props[$key] = $this->properties[$key];
|
||||
|
@ -728,7 +735,9 @@ abstract class DbObject
|
|||
{
|
||||
$properties = $this->getPropertiesForDb();
|
||||
if ($this->autoincKeyName !== null) {
|
||||
unset($properties[$this->autoincKeyName]);
|
||||
if ($this->protectAutoinc || $properties[$this->autoincKeyName] === null) {
|
||||
unset($properties[$this->autoincKeyName]);
|
||||
}
|
||||
}
|
||||
// TODO: Remove this!
|
||||
if ($this->connection->isPgsql()) {
|
||||
|
@ -785,15 +794,20 @@ abstract class DbObject
|
|||
}
|
||||
} else {
|
||||
if ($id && $this->existsInDb()) {
|
||||
$logId = '"' . $this->getLogId() . '"';
|
||||
|
||||
if ($autoId = $this->getAutoincId()) {
|
||||
$logId .= sprintf(', %s=%s', $this->autoincKeyName, $autoId);
|
||||
}
|
||||
throw new DuplicateKeyException(
|
||||
'Trying to recreate %s (%s)',
|
||||
$table,
|
||||
$this->getLogId()
|
||||
$logId
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->insertIntoDb()) {
|
||||
if ($this->autoincKeyName) {
|
||||
if ($this->autoincKeyName && $this->getProperty($this->autoincKeyName) === null) {
|
||||
if ($this->connection->isPgsql()) {
|
||||
$this->properties[$this->autoincKeyName] = $this->db->lastInsertId(
|
||||
$table,
|
||||
|
@ -885,6 +899,12 @@ abstract class DbObject
|
|||
public function createWhere()
|
||||
{
|
||||
if ($id = $this->getAutoincId()) {
|
||||
if ($originalId = $this->getOriginalProperty($this->autoincKeyName)) {
|
||||
return $this->db->quoteInto(
|
||||
sprintf('%s = ?', $this->autoincKeyName),
|
||||
$originalId
|
||||
);
|
||||
}
|
||||
return $this->db->quoteInto(
|
||||
sprintf('%s = ?', $this->autoincKeyName),
|
||||
$id
|
||||
|
|
|
@ -51,6 +51,8 @@ abstract class DbObjectWithSettings extends DbObject
|
|||
|
||||
public function getSettings()
|
||||
{
|
||||
// Sort them, important only for new objects
|
||||
ksort($this->settings);
|
||||
return $this->settings;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
namespace Icinga\Module\Director\Db;
|
||||
|
||||
use Exception;
|
||||
use Icinga\Exception\IcingaException;
|
||||
use Icinga\Module\Director\Data\Db\DbConnection;
|
||||
use RuntimeException;
|
||||
|
||||
class Migration
|
||||
{
|
||||
|
@ -27,21 +27,25 @@ class Migration
|
|||
/**
|
||||
* @param DbConnection $connection
|
||||
* @return $this
|
||||
* @throws IcingaException
|
||||
*/
|
||||
public function apply(DbConnection $connection)
|
||||
{
|
||||
/** @var \Zend_Db_Adapter_Pdo_Abstract $db */
|
||||
$db = $connection->getDbAdapter();
|
||||
|
||||
// TODO: this is fagile and depends on accordingly written schema files:
|
||||
$queries = preg_split('/[\n\s\t]*\;[\n\s\t]+/s', $this->sql, -1, PREG_SPLIT_NO_EMPTY);
|
||||
// TODO: this is fragile and depends on accordingly written schema files:
|
||||
$queries = preg_split(
|
||||
'/[\n\s\t]*\;[\n\s\t]+/s',
|
||||
$this->sql,
|
||||
-1,
|
||||
PREG_SPLIT_NO_EMPTY
|
||||
);
|
||||
|
||||
if (empty($queries)) {
|
||||
throw new IcingaException(
|
||||
throw new RuntimeException(sprintf(
|
||||
'Migration %d has no queries',
|
||||
$this->version
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -53,12 +57,12 @@ class Migration
|
|||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw new IcingaException(
|
||||
throw new RuntimeException(sprintf(
|
||||
'Migration %d failed (%s) while running %s',
|
||||
$this->version,
|
||||
$e->getMessage(),
|
||||
$query
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
|
|
@ -86,7 +86,6 @@ class Migrations
|
|||
|
||||
/**
|
||||
* @return $this
|
||||
* @throws \Icinga\Exception\IcingaException
|
||||
*/
|
||||
public function applyPendingMigrations()
|
||||
{
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\DirectorObject\Automation;
|
||||
|
||||
use Icinga\Module\Director\Core\Json;
|
||||
use Icinga\Module\Director\Data\Db\DbObject;
|
||||
use Icinga\Module\Director\Db;
|
||||
|
||||
/**
|
||||
* Class Basket
|
||||
*
|
||||
* TODO
|
||||
* - create a UUID like in RFC4122
|
||||
*/
|
||||
class Basket extends DbObject
|
||||
{
|
||||
const SELECTION_ALL = true;
|
||||
const SELECTION_NONE = false;
|
||||
|
||||
protected $validTypes = [
|
||||
'host_template',
|
||||
'host_object',
|
||||
'service_template',
|
||||
'service_object',
|
||||
'service_apply',
|
||||
'import_source',
|
||||
'sync_rule'
|
||||
];
|
||||
|
||||
protected $table = 'director_basket';
|
||||
|
||||
protected $keyName = 'basket_name';
|
||||
|
||||
protected $chosenObjects = [];
|
||||
|
||||
protected $defaultProperties = [
|
||||
'uuid' => null,
|
||||
'basket_name' => null,
|
||||
'objects' => null,
|
||||
'owner_type' => null,
|
||||
'owner_value' => null,
|
||||
];
|
||||
|
||||
public function getHexUuid()
|
||||
{
|
||||
return bin2hex($this->get('uuid'));
|
||||
}
|
||||
|
||||
public function listObjectTypes()
|
||||
{
|
||||
return array_keys($this->objects);
|
||||
}
|
||||
|
||||
public function getChosenObjects()
|
||||
{
|
||||
return $this->chosenObjects;
|
||||
}
|
||||
|
||||
public function isEmpty()
|
||||
{
|
||||
return count($this->getChosenObjects()) === 0;
|
||||
}
|
||||
|
||||
protected function onLoadFromDb()
|
||||
{
|
||||
$this->chosenObjects = (array) Json::decode($this->get('objects'));
|
||||
}
|
||||
|
||||
public function supportsCustomSelectionFor($type)
|
||||
{
|
||||
if (! array_key_exists($type, $this->chosenObjects)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_array($this->chosenObjects[$type]);
|
||||
}
|
||||
|
||||
public function setObjects($objects)
|
||||
{
|
||||
if (empty($objects)) {
|
||||
$this->chosenObjects = [];
|
||||
} else {
|
||||
$this->chosenObjects = [];
|
||||
foreach ((array) $objects as $type => $object) {
|
||||
$this->addObjects($type, $object);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a weird method, as it is required to deal with raw form data
|
||||
*
|
||||
* @param $type
|
||||
* @param ExportInterface[]|bool $objects
|
||||
*/
|
||||
public function addObjects($type, $objects = true)
|
||||
{
|
||||
BasketSnapshot::assertValidType($type);
|
||||
|
||||
// '1' -> from Form!
|
||||
if ($objects === 'ALL') {
|
||||
$objects = true;
|
||||
} elseif ($objects === null || $objects === 'IGNORE') {
|
||||
return;
|
||||
} elseif ($objects === '[]') {
|
||||
if (isset($this->chosenObjects[$type])) {
|
||||
if (! is_array($this->chosenObjects[$type])) {
|
||||
$this->chosenObjects[$type] = [];
|
||||
}
|
||||
} else {
|
||||
$this->chosenObjects[$type] = [];
|
||||
}
|
||||
$objects = [];
|
||||
}
|
||||
|
||||
if ($objects === true) {
|
||||
$this->chosenObjects[$type] = true;
|
||||
} elseif ($objects === '0') {
|
||||
// nothing
|
||||
} else {
|
||||
foreach ($objects as $object) {
|
||||
$this->addObject($type, $object);
|
||||
}
|
||||
|
||||
if (array_key_exists($type, $this->chosenObjects)) {
|
||||
ksort($this->chosenObjects[$type]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->reallySet('objects', Json::encode($this->chosenObjects));
|
||||
}
|
||||
|
||||
public function hasObject($type, $object)
|
||||
{
|
||||
if (! $this->hasType($type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->chosenObjects[$type] === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($object instanceof ExportInterface) {
|
||||
$object = $object->getUniqueIdentifier();
|
||||
}
|
||||
|
||||
if (is_array($this->chosenObjects[$type])) {
|
||||
return in_array($object, $this->chosenObjects[$type]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $type
|
||||
* @param string $object
|
||||
*/
|
||||
public function addObject($type, $object)
|
||||
{
|
||||
if (is_array($this->chosenObjects[$type])) {
|
||||
$this->chosenObjects[$type][] = $object;
|
||||
} else {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'The Basket "%s" has not been configured for single objects of type "%s"',
|
||||
$this->get('basket_name'),
|
||||
$type
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public function hasType($type)
|
||||
{
|
||||
return isset($this->chosenObjects[$type]);
|
||||
}
|
||||
|
||||
protected function beforeStore()
|
||||
{
|
||||
if (! $this->hasBeenLoadedFromDb()) {
|
||||
// TODO: This is BS, use a real UUID
|
||||
$this->set('uuid', hex2bin(substr(sha1(microtime(true) . rand(1, 100000)), 0, 32)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\DirectorObject\Automation;
|
||||
|
||||
use Icinga\Module\Director\Data\Db\DbObject;
|
||||
|
||||
class BasketContent extends DbObject
|
||||
{
|
||||
protected $objects;
|
||||
|
||||
protected $table = 'director_basket_content';
|
||||
|
||||
protected $keyName = 'checksum';
|
||||
|
||||
protected $defaultProperties = [
|
||||
'checksum' => null,
|
||||
'summary' => null,
|
||||
'content' => null,
|
||||
];
|
||||
}
|
|
@ -0,0 +1,396 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\DirectorObject\Automation;
|
||||
|
||||
use Icinga\Module\Director\Core\Json;
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\Data\Db\DbObject;
|
||||
use Icinga\Module\Director\Objects\DirectorDatafield;
|
||||
use Icinga\Module\Director\Objects\IcingaCommand;
|
||||
use Icinga\Module\Director\Objects\IcingaObject;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
class BasketSnapshot extends DbObject
|
||||
{
|
||||
protected static $typeClasses = [
|
||||
'Datafield' => '\\Icinga\\Module\\Director\\Objects\\DirectorDatafield',
|
||||
'Command' => '\\Icinga\\Module\\Director\\Objects\\IcingaCommand',
|
||||
'HostGroup' => '\\Icinga\\Module\\Director\\Objects\\IcingaHostGroup',
|
||||
'IcingaTemplateChoiceHost' => '\\Icinga\\Module\\Director\\Objects\\IcingaTemplateChoiceHost',
|
||||
'HostTemplate' => '\\Icinga\\Module\\Director\\Objects\\IcingaHost',
|
||||
'ServiceGroup' => '\\Icinga\\Module\\Director\\Objects\\IcingaServiceGroup',
|
||||
'IcingaTemplateChoiceService' => '\\Icinga\\Module\\Director\\Objects\\IcingaTemplateChoiceService',
|
||||
'ServiceTemplate' => '\\Icinga\\Module\\Director\\Objects\\IcingaService',
|
||||
'ServiceSet' => '\\Icinga\\Module\\Director\\Objects\\IcingaServiceSet',
|
||||
'Notification' => '\\Icinga\\Module\\Director\\Objects\\IcingaNotification',
|
||||
'Dependency' => '\\Icinga\\Module\\Director\\Objects\\IcingaDependency',
|
||||
'ImportSource' => '\\Icinga\\Module\\Director\\Objects\\ImportSource',
|
||||
'SyncRule' => '\\Icinga\\Module\\Director\\Objects\\SyncRule',
|
||||
'DirectorJob' => '\\Icinga\\Module\\Director\\Objects\\DirectorJob',
|
||||
'Basket' => '\\Icinga\\Module\\Director\\DirectorObject\\Automation\\Automation',
|
||||
];
|
||||
|
||||
protected $objects = [];
|
||||
|
||||
protected $content;
|
||||
|
||||
protected $table = 'director_basket_snapshot';
|
||||
|
||||
protected $keyName = [
|
||||
'basket_uuid',
|
||||
'ts_create',
|
||||
];
|
||||
|
||||
protected $restoreOrder = [
|
||||
'Command',
|
||||
'HostGroup',
|
||||
'IcingaTemplateChoiceHost',
|
||||
'HostTemplate',
|
||||
'ServiceGroup',
|
||||
'IcingaTemplateChoiceService',
|
||||
'ServiceTemplate',
|
||||
'ServiceSet',
|
||||
'Notification',
|
||||
'Dependency',
|
||||
'ImportSource',
|
||||
'SyncRule',
|
||||
'DirectorJob',
|
||||
'Basket',
|
||||
];
|
||||
|
||||
protected $defaultProperties = [
|
||||
'basket_uuid' => null,
|
||||
'content_checksum' => null,
|
||||
'ts_create' => null,
|
||||
];
|
||||
|
||||
public static function supports($type)
|
||||
{
|
||||
return isset(self::$typeClasses[$type]);
|
||||
}
|
||||
|
||||
public static function assertValidType($type)
|
||||
{
|
||||
if (! static::supports($type)) {
|
||||
throw new InvalidArgumentException("Basket does not support '$type'");
|
||||
}
|
||||
}
|
||||
|
||||
public static function getClassForType($type)
|
||||
{
|
||||
static::assertValidType($type);
|
||||
|
||||
return self::$typeClasses[$type];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Basket $basket
|
||||
* @param Db $db
|
||||
* @return BasketSnapshot
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public static function createForBasket(Basket $basket, Db $db)
|
||||
{
|
||||
$snapshot = static::create([
|
||||
'basket_uuid' => $basket->get('uuid')
|
||||
], $db);
|
||||
$snapshot->addObjectsChosenByBasket($basket);
|
||||
$snapshot->resolveRequiredFields();
|
||||
|
||||
return $snapshot;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
protected function resolveRequiredFields()
|
||||
{
|
||||
/** @var Db $db */
|
||||
$db = $this->getConnection();
|
||||
$fieldResolver = new BasketSnapshotFieldResolver($this->objects, $db);
|
||||
/** @var DirectorDatafield[] $fields */
|
||||
$fields = $fieldResolver->loadCurrentFields($db);
|
||||
if (! empty($fields)) {
|
||||
$plain = [];
|
||||
foreach ($fields as $id => $field) {
|
||||
$plain[$id] = $field->export();
|
||||
}
|
||||
$this->objects['Datafield'] = $plain;
|
||||
}
|
||||
}
|
||||
|
||||
protected function addObjectsChosenByBasket(Basket $basket)
|
||||
{
|
||||
foreach ($basket->getChosenObjects() as $typeName => $selection) {
|
||||
if ($selection === true) {
|
||||
$this->addAll($typeName);
|
||||
} elseif (! empty($selection)) {
|
||||
$this->addByIdentifiers($typeName, $selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Module\Director\Exception\DuplicateKeyException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
protected function beforeStore()
|
||||
{
|
||||
if ($this->hasBeenLoadedFromDb()) {
|
||||
throw new RuntimeException('A basket snapshot cannot be modified');
|
||||
}
|
||||
$json = $this->getJsonDump();
|
||||
$checksum = sha1($json, true);
|
||||
if (! BasketContent::exists($checksum, $this->getConnection())) {
|
||||
BasketContent::create([
|
||||
'checksum' => $checksum,
|
||||
'summary' => $this->getJsonSummary(),
|
||||
'content' => $json,
|
||||
], $this->getConnection())->store();
|
||||
}
|
||||
|
||||
$this->set('content_checksum', $checksum);
|
||||
$this->set('ts_create', round(microtime(true) * 1000));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Db $connection
|
||||
* @param bool $replace
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function restoreTo(Db $connection, $replace = true)
|
||||
{
|
||||
static::restoreJson(
|
||||
$this->getJsonDump(),
|
||||
$connection,
|
||||
$replace
|
||||
);
|
||||
}
|
||||
|
||||
public static function restoreJson($string, Db $connection, $replace = true)
|
||||
{
|
||||
$snapshot = new static();
|
||||
$snapshot->restoreObjects(
|
||||
Json::decode($string),
|
||||
$connection,
|
||||
$replace
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $all
|
||||
* @param Db $connection
|
||||
* @param bool $replace
|
||||
* @throws \Icinga\Module\Director\Exception\DuplicateKeyException
|
||||
* @throws \Zend_Db_Adapter_Exception
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
protected function restoreObjects($all, Db $connection, $replace = true)
|
||||
{
|
||||
$db = $connection->getDbAdapter();
|
||||
$db->beginTransaction();
|
||||
$fieldResolver = new BasketSnapshotFieldResolver($all, $connection);
|
||||
$fieldResolver->storeNewFields();
|
||||
foreach ($this->restoreOrder as $typeName) {
|
||||
if (isset($all->$typeName)) {
|
||||
$objects = $all->$typeName;
|
||||
$class = static::getClassForType($typeName);
|
||||
|
||||
$changed = [];
|
||||
foreach ($objects as $key => $object) {
|
||||
/** @var DbObject $new */
|
||||
$new = $class::import($object, $connection, $replace);
|
||||
if ($new->hasBeenModified()) {
|
||||
if ($new instanceof IcingaObject && $new->supportsImports()) {
|
||||
/** @var ExportInterface $new */
|
||||
$changed[$new->getUniqueIdentifier()] = $new;
|
||||
} else {
|
||||
$new->store();
|
||||
// Linking fields right now, as we're not in $changed
|
||||
if ($new instanceof IcingaObject) {
|
||||
$fieldResolver->relinkObjectFields($new, $object);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No modification on the object, still, fields might have
|
||||
// been changed
|
||||
if ($new instanceof IcingaObject) {
|
||||
$fieldResolver->relinkObjectFields($new, $object);
|
||||
}
|
||||
}
|
||||
$allObjects[spl_object_hash($new)] = $object;
|
||||
}
|
||||
|
||||
/** @var IcingaObject $object */
|
||||
foreach ($changed as $object) {
|
||||
$this->recursivelyStore($object, $changed);
|
||||
}
|
||||
foreach ($changed as $key => $new) {
|
||||
// Store related fields. As objects might have formerly been
|
||||
// unstored, let's to it right here
|
||||
if ($new instanceof IcingaObject) {
|
||||
$fieldResolver->relinkObjectFields($new, $objects[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$db->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IcingaObject $object
|
||||
* @param $list
|
||||
* @throws \Icinga\Module\Director\Exception\DuplicateKeyException
|
||||
*/
|
||||
protected function recursivelyStore(IcingaObject $object, & $list)
|
||||
{
|
||||
foreach ($object->listImportNames() as $parent) {
|
||||
if (array_key_exists($parent, $list)) {
|
||||
$this->recursivelyStore($list[$parent], $list);
|
||||
}
|
||||
}
|
||||
|
||||
$object->store();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BasketContent
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
protected function getContent()
|
||||
{
|
||||
if ($this->content === null) {
|
||||
$this->content = BasketContent::load($this->get('content_checksum'), $this->getConnection());
|
||||
}
|
||||
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
protected function onDelete()
|
||||
{
|
||||
$db = $this->getDb();
|
||||
$db->delete(
|
||||
['bc' => 'director_basket_content'],
|
||||
'NOT EXISTS (SELECT director_basket_checksum WHERE content_checksum = bc.checksum)'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function getJsonSummary()
|
||||
{
|
||||
if ($this->hasBeenLoadedFromDb()) {
|
||||
return $this->getContent()->get('summary');
|
||||
} else {
|
||||
return Json::encode($this->getSummary(), JSON_PRETTY_PRINT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|mixed
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function getSummary()
|
||||
{
|
||||
if ($this->hasBeenLoadedFromDb()) {
|
||||
return Json::decode($this->getContent()->get('summary'));
|
||||
} else {
|
||||
$summary = [];
|
||||
foreach (array_keys($this->objects) as $key) {
|
||||
$summary[$key] = count($this->objects[$key]);
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function getJsonDump()
|
||||
{
|
||||
if ($this->hasBeenLoadedFromDb()) {
|
||||
return $this->getContent()->get('content');
|
||||
} else {
|
||||
return Json::encode($this->objects, JSON_PRETTY_PRINT);
|
||||
}
|
||||
}
|
||||
|
||||
protected function addAll($typeName)
|
||||
{
|
||||
$class = static::getClassForType($typeName);
|
||||
/** @var IcingaObject $dummy */
|
||||
$dummy = $class::create();
|
||||
/** @var ExportInterface $object */
|
||||
if ($dummy instanceof IcingaObject && $dummy->supportsImports()) {
|
||||
$db = $this->getDb();
|
||||
if ($dummy instanceof IcingaCommand) {
|
||||
$select = $db->select()->from($dummy->getTableName())
|
||||
->where('object_type != ?', 'external_object');
|
||||
} elseif (! $dummy->isGroup()) {
|
||||
$select = $db->select()->from($dummy->getTableName())
|
||||
->where('object_type = ?', 'template');
|
||||
} else {
|
||||
$select = $db->select()->from($dummy->getTableName());
|
||||
}
|
||||
$all = $class::loadAll($this->getConnection(), $select);
|
||||
} else {
|
||||
$all = $class::loadAll($this->getConnection());
|
||||
}
|
||||
foreach ($all as $object) {
|
||||
$this->objects[$typeName][$object->getUniqueIdentifier()] = $object->export();
|
||||
}
|
||||
}
|
||||
|
||||
protected function addByIdentifiers($typeName, $identifiers)
|
||||
{
|
||||
foreach ($identifiers as $identifier) {
|
||||
$this->addByIdentifier($typeName, $identifier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $typeName
|
||||
* @param $identifier
|
||||
* @param Db $connection
|
||||
* @return ExportInterface|null
|
||||
*/
|
||||
public static function instanceByIdentifier($typeName, $identifier, Db $connection)
|
||||
{
|
||||
$class = static::getClassForType($typeName);
|
||||
if (substr($class, -13) === 'IcingaService') {
|
||||
$identifier = [
|
||||
'object_type' => 'template',
|
||||
'object_name' => $identifier,
|
||||
];
|
||||
}
|
||||
/** @var ExportInterface $object */
|
||||
if ($class::exists($identifier, $connection)) {
|
||||
$object = $class::load($identifier, $connection);
|
||||
} else {
|
||||
$object = null;
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $typeName
|
||||
* @param $identifier
|
||||
*/
|
||||
protected function addByIdentifier($typeName, $identifier)
|
||||
{
|
||||
/** @var Db $connection */
|
||||
$connection = $this->getConnection();
|
||||
$object = static::instanceByIdentifier(
|
||||
$typeName,
|
||||
$identifier,
|
||||
$connection
|
||||
);
|
||||
$this->objects[$typeName][$identifier] = $object->export();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\DirectorObject\Automation;
|
||||
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\Objects\DirectorDatafield;
|
||||
use Icinga\Module\Director\Objects\IcingaObject;
|
||||
|
||||
class BasketSnapshotFieldResolver
|
||||
{
|
||||
/** @var BasketSnapshot */
|
||||
protected $snapshot;
|
||||
|
||||
/** @var \Icinga\Module\Director\Data\Db\DbConnection */
|
||||
protected $targetDb;
|
||||
|
||||
/** @var array|null */
|
||||
protected $requiredIds;
|
||||
|
||||
protected $objects;
|
||||
|
||||
/** @var int */
|
||||
protected $nextNewId = 1;
|
||||
|
||||
/** @var array|null */
|
||||
protected $idMap;
|
||||
|
||||
/** @var DirectorDatafield[]|null */
|
||||
protected $targetFields;
|
||||
|
||||
public function __construct($objects, Db $targetDb)
|
||||
{
|
||||
$this->objects = $objects;
|
||||
$this->targetDb = $targetDb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Db $db
|
||||
* @return DirectorDatafield[]
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function loadCurrentFields(Db $db)
|
||||
{
|
||||
$fields = [];
|
||||
foreach ($this->getRequiredIds() as $id) {
|
||||
$fields[$id] = DirectorDatafield::loadWithAutoIncId((int) $id, $db);
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
* @throws \Icinga\Module\Director\Exception\DuplicateKeyException
|
||||
*/
|
||||
public function storeNewFields()
|
||||
{
|
||||
foreach ($this->getTargetFields() as $id => $field) {
|
||||
if ($field->hasBeenModified()) {
|
||||
$field->store();
|
||||
$this->idMap[$id] = $field->get('id');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IcingaObject $new
|
||||
* @param $object
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
* @throws \Zend_Db_Adapter_Exception
|
||||
*/
|
||||
public function relinkObjectFields(IcingaObject $new, $object)
|
||||
{
|
||||
if (! $new->supportsFields() || ! isset($object->fields)) {
|
||||
return;
|
||||
}
|
||||
$fieldMap = $this->getIdMap();
|
||||
|
||||
$objectId = (int) $new->get('id');
|
||||
$table = $new->getTableName() . '_field';
|
||||
$objectKey = $new->getShortTableName() . '_id';
|
||||
$existingFields = [];
|
||||
|
||||
$db = $this->targetDb->getDbAdapter();
|
||||
|
||||
foreach ($db->fetchAll(
|
||||
$db->select()->from($table)->where("$objectKey = ?", $objectId)
|
||||
) as $mapping) {
|
||||
$existingFields[(int) $mapping->datafield_id] = $mapping;
|
||||
}
|
||||
foreach ($object->fields as $field) {
|
||||
$id = $fieldMap[(int) $field->datafield_id];
|
||||
if (isset($existingFields[$id])) {
|
||||
unset($existingFields[$id]);
|
||||
} else {
|
||||
$db->insert($table, [
|
||||
$objectKey => $objectId,
|
||||
'datafield_id' => $id,
|
||||
'is_required' => $field->is_required,
|
||||
'var_filter' => $field->var_filter,
|
||||
]);
|
||||
}
|
||||
}
|
||||
if (! empty($existingFields)) {
|
||||
$db->delete(
|
||||
$table,
|
||||
$db->quoteInto(
|
||||
"$objectKey = $objectId AND datafield_id IN (?)",
|
||||
array_keys($existingFields)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $object
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function tweakTargetIds($object)
|
||||
{
|
||||
$forward = $this->getIdMap();
|
||||
$map = array_flip($forward);
|
||||
if (isset($object->fields)) {
|
||||
foreach ($object->fields as $field) {
|
||||
$id = $field->datafield_id;
|
||||
if (isset($map[$id])) {
|
||||
$field->datafield_id = $map[$id];
|
||||
} else {
|
||||
$field->datafield_id = "(NEW)";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
protected function getNextNewId()
|
||||
{
|
||||
return $this->nextNewId++;
|
||||
}
|
||||
|
||||
protected function getRequiredIds()
|
||||
{
|
||||
if ($this->requiredIds === null) {
|
||||
if (isset($this->objects['Datafield'])) {
|
||||
$this->requiredIds = array_keys($this->objects['Datafield']);
|
||||
} else {
|
||||
$ids = [];
|
||||
foreach ($this->objects as $typeName => $objects) {
|
||||
foreach ($objects as $key => $object) {
|
||||
if (isset($object->fields)) {
|
||||
foreach ($object->fields as $field) {
|
||||
$ids[$field->datafield_id] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->requiredIds = array_keys($ids);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->requiredIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $type
|
||||
* @return object[]
|
||||
*/
|
||||
protected function getObjectsByType($type)
|
||||
{
|
||||
if (isset($this->objects->$type)) {
|
||||
return $this->objects->$type;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DirectorDatafield[]
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
protected function getTargetFields()
|
||||
{
|
||||
if ($this->targetFields === null) {
|
||||
$this->calculateIdMap();
|
||||
}
|
||||
|
||||
return $this->targetFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
protected function getIdMap()
|
||||
{
|
||||
if ($this->idMap === null) {
|
||||
$this->calculateIdMap();
|
||||
}
|
||||
|
||||
return $this->idMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
protected function calculateIdMap()
|
||||
{
|
||||
$this->idMap = [];
|
||||
$this->targetFields = [];
|
||||
foreach ($this->getObjectsByType('Datafield') as $id => $object) {
|
||||
// Hint: import() doesn't store!
|
||||
$new = DirectorDatafield::import($object, $this->targetDb);
|
||||
if ($new->hasBeenLoadedFromDb()) {
|
||||
$newId = (int) $new->get('id');
|
||||
} else {
|
||||
$newId = sprintf('NEW(%s)', $this->getNextNewId());
|
||||
}
|
||||
$this->idMap[$id] = $newId;
|
||||
$this->targetFields[$id] = $new;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\DirectorObject\Automation;
|
||||
|
||||
interface ExportInterface
|
||||
{
|
||||
/**
|
||||
* @return \stdClass
|
||||
*/
|
||||
public function export();
|
||||
|
||||
// TODO:
|
||||
// public function getXyzChecksum();
|
||||
public function getUniqueIdentifier();
|
||||
}
|
|
@ -8,6 +8,7 @@ use Icinga\Module\Director\Objects\DirectorDatalist;
|
|||
use Icinga\Module\Director\Objects\DirectorJob;
|
||||
use Icinga\Module\Director\Objects\IcingaHostGroup;
|
||||
use Icinga\Module\Director\Objects\IcingaServiceGroup;
|
||||
use Icinga\Module\Director\Objects\IcingaServiceSet;
|
||||
use Icinga\Module\Director\Objects\IcingaTemplateChoiceHost;
|
||||
use Icinga\Module\Director\Objects\ImportSource;
|
||||
use Icinga\Module\Director\Objects\SyncRule;
|
||||
|
@ -21,6 +22,21 @@ class ImportExport
|
|||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
public function serializeAllServiceSets()
|
||||
{
|
||||
// TODO: Export host templates in Inheritance order
|
||||
$res = [];
|
||||
$related = [];
|
||||
foreach (IcingaServiceSet::loadAll($this->connection) as $object) {
|
||||
$res[] = $object->export();
|
||||
foreach ($object->exportRelated() as $key => $relatedObject) {
|
||||
$related[$key] = $relatedObject;
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function serializeAllHostTemplateChoices()
|
||||
{
|
||||
$res = [];
|
||||
|
|
|
@ -210,7 +210,10 @@ class Sync
|
|||
foreach ($this->syncProperties as $p) {
|
||||
$id = $p->source_id;
|
||||
if (! array_key_exists($id, $this->sources)) {
|
||||
$this->sources[$id] = ImportSource::load($id, $this->db);
|
||||
$this->sources[$id] = ImportSource::loadWithAutoIncId(
|
||||
(int) $id,
|
||||
$this->db
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,10 @@ use Icinga\Module\Director\Web\Form\QuickForm;
|
|||
|
||||
class ImportJob extends JobHook
|
||||
{
|
||||
/**
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
* @throws \Icinga\Module\Director\Exception\DuplicateKeyException
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$db = $this->db();
|
||||
|
@ -18,10 +22,14 @@ class ImportJob extends JobHook
|
|||
$this->runForSource($source);
|
||||
}
|
||||
} else {
|
||||
$this->runForSource(ImportSource::load($id, $db));
|
||||
$this->runForSource(ImportSource::loadWithAutoIncId($id, $db));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function exportSettings()
|
||||
{
|
||||
$settings = parent::exportSettings();
|
||||
|
@ -40,6 +48,10 @@ class ImportJob extends JobHook
|
|||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ImportSource $source
|
||||
* @throws \Icinga\Module\Director\Exception\DuplicateKeyException
|
||||
*/
|
||||
protected function runForSource(ImportSource $source)
|
||||
{
|
||||
if ($this->getSetting('run_import') === 'y') {
|
||||
|
@ -56,6 +68,10 @@ class ImportJob extends JobHook
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QuickForm $form
|
||||
* @throws \Zend_Form_Exception
|
||||
*/
|
||||
public static function addSettingsFormFields(QuickForm $form)
|
||||
{
|
||||
$rules = self::enumImportSources($form);
|
||||
|
|
|
@ -11,6 +11,10 @@ class SyncJob extends JobHook
|
|||
{
|
||||
protected $rule;
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
* @throws \Icinga\Module\Director\Exception\DuplicateKeyException
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$db = $this->db();
|
||||
|
@ -20,10 +24,14 @@ class SyncJob extends JobHook
|
|||
$this->runForRule($rule);
|
||||
}
|
||||
} else {
|
||||
$this->runForRule(SyncRule::load($id, $db));
|
||||
$this->runForRule(SyncRule::loadWithAutoIncId((int) $id, $db));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function exportSettings()
|
||||
{
|
||||
$settings = [
|
||||
|
@ -31,13 +39,17 @@ class SyncJob extends JobHook
|
|||
];
|
||||
$id = $this->getSetting('rule_id');
|
||||
if ($id !== '__ALL__') {
|
||||
$settings['rule'] = SyncRule::load((int) $id, $this->db())
|
||||
$settings['rule'] = SyncRule::loadWithAutoIncId((int) $id, $this->db())
|
||||
->get('rule_name');
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SyncRule $rule
|
||||
* @throws \Icinga\Module\Director\Exception\DuplicateKeyException
|
||||
*/
|
||||
protected function runForRule(SyncRule $rule)
|
||||
{
|
||||
if ($this->getSetting('apply_changes') === 'y') {
|
||||
|
@ -54,6 +66,11 @@ class SyncJob extends JobHook
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QuickForm $form
|
||||
* @return DirectorObjectForm|QuickForm
|
||||
* @throws \Zend_Form_Exception
|
||||
*/
|
||||
public static function addSettingsFormFields(QuickForm $form)
|
||||
{
|
||||
/** @var DirectorObjectForm $form */
|
||||
|
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
namespace Icinga\Module\Director\Objects;
|
||||
|
||||
use Icinga\Module\Director\Core\Json;
|
||||
use Icinga\Module\Director\Data\Db\DbObjectWithSettings;
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\Exception\DuplicateKeyException;
|
||||
use Icinga\Module\Director\Hook\DataTypeHook;
|
||||
use Icinga\Module\Director\Web\Form\DirectorObjectForm;
|
||||
use InvalidArgumentException;
|
||||
use Zend_Form_Element as ZfElement;
|
||||
|
||||
class DirectorDatafield extends DbObjectWithSettings
|
||||
|
@ -50,6 +53,10 @@ class DirectorDatafield extends DbObjectWithSettings
|
|||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
$plain = (object) $this->getProperties();
|
||||
|
@ -68,6 +75,61 @@ class DirectorDatafield extends DbObjectWithSettings
|
|||
return $plain;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $plain
|
||||
* @param Db $db
|
||||
* @param bool $replace
|
||||
* @return DirectorDatafield
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public static function import($plain, Db $db, $replace = false)
|
||||
{
|
||||
$properties = (array) $plain;
|
||||
if (isset($properties['originalId'])) {
|
||||
$id = $properties['originalId'];
|
||||
unset($properties['originalId']);
|
||||
} else {
|
||||
$id = null;
|
||||
}
|
||||
|
||||
if (isset($properties['settings']->datalist)) {
|
||||
$list = DirectorDatalist::load(
|
||||
$properties['settings']->datalist,
|
||||
$db
|
||||
);
|
||||
$properties['settings']->datalist_id = $list->get('id');
|
||||
unset($properties['settings']->datalist);
|
||||
}
|
||||
|
||||
$encoded = Json::encode($properties);
|
||||
if ($id) {
|
||||
if (static::exists($id, $db)) {
|
||||
$existing = static::loadWithAutoIncId($id, $db);
|
||||
$existingProperties = (array) $existing->export();
|
||||
unset($existingProperties['originalId']);
|
||||
if ($encoded === Json::encode($existingProperties)) {
|
||||
return $existing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dba = $db->getDbAdapter();
|
||||
$query = $dba->select()
|
||||
->from('director_datafield')
|
||||
->where('varname = ?', $plain->varname);
|
||||
$candidates = DirectorDatafield::loadAll($db, $query);
|
||||
|
||||
foreach ($candidates as $candidate) {
|
||||
$export = $candidate->export();
|
||||
unset($export->originalId);
|
||||
if (Json::encode($export) === $encoded) {
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return static::create($properties, $db);
|
||||
}
|
||||
|
||||
protected function setObject(IcingaObject $object)
|
||||
{
|
||||
$this->object = $object;
|
||||
|
|
|
@ -2,22 +2,28 @@
|
|||
|
||||
namespace Icinga\Module\Director\Objects;
|
||||
|
||||
use Icinga\Exception\NotFoundError;
|
||||
use Icinga\Module\Director\Data\Db\DbObjectWithSettings;
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
|
||||
use Icinga\Module\Director\Exception\DuplicateKeyException;
|
||||
use Icinga\Module\Director\Hook\JobHook;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class DirectorJob extends DbObjectWithSettings
|
||||
class DirectorJob extends DbObjectWithSettings implements ExportInterface
|
||||
{
|
||||
/** @var JobHook */
|
||||
protected $job;
|
||||
|
||||
protected $table = 'director_job';
|
||||
|
||||
protected $keyName = 'id';
|
||||
protected $keyName = 'job_name';
|
||||
|
||||
protected $autoincKeyName = 'id';
|
||||
|
||||
protected $protectAutoinc = false;
|
||||
|
||||
protected $defaultProperties = [
|
||||
'id' => null,
|
||||
'job_name' => null,
|
||||
|
@ -42,6 +48,11 @@ class DirectorJob extends DbObjectWithSettings
|
|||
|
||||
protected $settingsRemoteId = 'job_id';
|
||||
|
||||
public function getUniqueIdentifier()
|
||||
{
|
||||
return $this->get('job_name');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JobHook
|
||||
*/
|
||||
|
@ -188,6 +199,71 @@ class DirectorJob extends DbObjectWithSettings
|
|||
return $plain;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $plain
|
||||
* @param Db $db
|
||||
* @param bool $replace
|
||||
* @return DirectorJob
|
||||
* @throws DuplicateKeyException
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
public static function import($plain, Db $db, $replace = false)
|
||||
{
|
||||
$dummy = new static;
|
||||
$idCol = $dummy->autoincKeyName;
|
||||
$keyCol = $dummy->keyName;
|
||||
$properties = (array) $plain;
|
||||
if (isset($properties['originalId'])) {
|
||||
$id = $properties['originalId'];
|
||||
unset($properties['originalId']);
|
||||
} else {
|
||||
$id = null;
|
||||
}
|
||||
$name = $properties[$keyCol];
|
||||
|
||||
if ($replace && static::existsWithNameAndId($name, $id, $db)) {
|
||||
$object = static::loadWithAutoIncId($id, $db);
|
||||
} elseif ($replace && static::exists($name, $db)) {
|
||||
$object = static::load($name, $db);
|
||||
} elseif (static::exists($name, $db)) {
|
||||
throw new DuplicateKeyException(
|
||||
'Director Job "%s" already exists',
|
||||
$name
|
||||
);
|
||||
} else {
|
||||
$object = static::create([], $db);
|
||||
}
|
||||
|
||||
$object->setProperties($properties);
|
||||
if ($id !== null) {
|
||||
$object->reallySet($idCol, $id);
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param int $id
|
||||
* @param Db $connection
|
||||
* @api internal
|
||||
* @return bool
|
||||
*/
|
||||
protected static function existsWithNameAndId($name, $id, Db $connection)
|
||||
{
|
||||
$db = $connection->getDbAdapter();
|
||||
$dummy = new static;
|
||||
$idCol = $dummy->autoincKeyName;
|
||||
$keyCol = $dummy->keyName;
|
||||
|
||||
return (string) $id === (string) $db->fetchOne(
|
||||
$db->select()
|
||||
->from($dummy->table, $idCol)
|
||||
->where("$idCol = ?", $id)
|
||||
->where("$keyCol = ?", $name)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IcingaTimePeriod
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
|
|
|
@ -2,12 +2,15 @@
|
|||
|
||||
namespace Icinga\Module\Director\Objects;
|
||||
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
|
||||
use Icinga\Module\Director\Exception\DuplicateKeyException;
|
||||
use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c;
|
||||
use Icinga\Module\Director\IcingaConfig\IcingaLegacyConfigHelper as c1;
|
||||
use Icinga\Module\Director\Objects\Extension\Arguments;
|
||||
use Zend_Db_Select as DbSelect;
|
||||
|
||||
class IcingaCommand extends IcingaObject implements ObjectWithArguments
|
||||
class IcingaCommand extends IcingaObject implements ObjectWithArguments, ExportInterface
|
||||
{
|
||||
use Arguments;
|
||||
|
||||
|
@ -197,6 +200,59 @@ class IcingaCommand extends IcingaObject implements ObjectWithArguments
|
|||
return $this->countDirectUses() > 0;
|
||||
}
|
||||
|
||||
public function getUniqueIdentifier()
|
||||
{
|
||||
return $this->getObjectName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
$object = $this->toPlainObject();
|
||||
if (property_exists($object, 'arguments')) {
|
||||
foreach ($object->arguments as $key => $argument) {
|
||||
if (property_exists($argument, 'command_id')) {
|
||||
unset($argument->command_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $plain
|
||||
* @param Db $db
|
||||
* @param bool $replace
|
||||
* @return IcingaCommand
|
||||
* @throws DuplicateKeyException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public static function import($plain, Db $db, $replace = false)
|
||||
{
|
||||
$properties = (array) $plain;
|
||||
$name = $properties['object_name'];
|
||||
$key = $name;
|
||||
|
||||
if ($replace && static::exists($key, $db)) {
|
||||
$object = static::load($key, $db);
|
||||
} elseif (static::exists($key, $db)) {
|
||||
throw new DuplicateKeyException(
|
||||
'Command "%s" already exists',
|
||||
$name
|
||||
);
|
||||
} else {
|
||||
$object = static::create([], $db);
|
||||
}
|
||||
|
||||
$object->setProperties($properties);
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
protected function renderCommand()
|
||||
{
|
||||
$command = $this->get('command');
|
||||
|
|
|
@ -6,11 +6,15 @@ use Icinga\Data\Db\DbConnection;
|
|||
use Icinga\Exception\NotFoundError;
|
||||
use Icinga\Module\Director\Data\PropertiesFilter;
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
|
||||
use Icinga\Module\Director\Exception\DuplicateKeyException;
|
||||
use Icinga\Module\Director\IcingaConfig\IcingaConfig;
|
||||
use Icinga\Module\Director\IcingaConfig\IcingaLegacyConfigHelper as c1;
|
||||
use Icinga\Module\Director\Objects\Extension\FlappingSupport;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
class IcingaHost extends IcingaObject
|
||||
class IcingaHost extends IcingaObject implements ExportInterface
|
||||
{
|
||||
use FlappingSupport;
|
||||
|
||||
|
@ -253,6 +257,96 @@ class IcingaHost extends IcingaObject
|
|||
}
|
||||
}
|
||||
|
||||
public function getUniqueIdentifier()
|
||||
{
|
||||
if ($this->isTemplate()) {
|
||||
return $this->getObjectName();
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
'getUniqueIdentifier() is supported by Host Templates only'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
// TODO: ksort in toPlainObject?
|
||||
$props = (array) $this->toPlainObject();
|
||||
$props['fields'] = $this->loadFieldReferences();
|
||||
ksort($props);
|
||||
|
||||
return (object) $props;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $plain
|
||||
* @param Db $db
|
||||
* @param bool $replace
|
||||
* @return IcingaHost
|
||||
* @throws DuplicateKeyException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public static function import($plain, Db $db, $replace = false)
|
||||
{
|
||||
$properties = (array) $plain;
|
||||
$name = $properties['object_name'];
|
||||
if ($properties['object_type'] !== 'template') {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Can import only Templates, got "%s" for "%s"',
|
||||
$properties['object_type'],
|
||||
$name
|
||||
));
|
||||
}
|
||||
$key = $name;
|
||||
|
||||
if ($replace && static::exists($key, $db)) {
|
||||
$object = static::load($key, $db);
|
||||
} elseif (static::exists($key, $db)) {
|
||||
throw new DuplicateKeyException(
|
||||
'Service Template "%s" already exists',
|
||||
$name
|
||||
);
|
||||
} else {
|
||||
$object = static::create([], $db);
|
||||
}
|
||||
|
||||
// $object->newFields = $properties['fields'];
|
||||
unset($properties['fields']);
|
||||
$object->setProperties($properties);
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
protected function loadFieldReferences()
|
||||
{
|
||||
$db = $this->getDb();
|
||||
|
||||
$res = $db->fetchAll(
|
||||
$db->select()->from([
|
||||
'hf' => 'icinga_host_field'
|
||||
], [
|
||||
'hf.datafield_id',
|
||||
'hf.is_required',
|
||||
'hf.var_filter',
|
||||
])->join(['df' => 'director_datafield'], 'df.id = hf.datafield_id', [])
|
||||
->where('host_id = ?', $this->get('id'))
|
||||
->order('varname ASC')
|
||||
);
|
||||
|
||||
if (empty($res)) {
|
||||
return [];
|
||||
} else {
|
||||
foreach ($res as $field) {
|
||||
$field->datafield_id = (int) $field->datafield_id;
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
||||
public function hasAnyOverridenServiceVars()
|
||||
{
|
||||
$varname = $this->getServiceOverrivesVarname();
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
|
||||
namespace Icinga\Module\Director\Objects;
|
||||
|
||||
abstract class IcingaObjectGroup extends IcingaObject
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
|
||||
use Icinga\Module\Director\Exception\DuplicateKeyException;
|
||||
|
||||
abstract class IcingaObjectGroup extends IcingaObject implements ExportInterface
|
||||
{
|
||||
protected $supportsImports = true;
|
||||
|
||||
|
@ -17,6 +21,50 @@ abstract class IcingaObjectGroup extends IcingaObject
|
|||
'assign_filter' => null,
|
||||
];
|
||||
|
||||
public function getUniqueIdentifier()
|
||||
{
|
||||
return $this->getObjectName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
return $this->toPlainObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $plain
|
||||
* @param Db $db
|
||||
* @param bool $replace
|
||||
* @return IcingaObjectGroup
|
||||
* @throws DuplicateKeyException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public static function import($plain, Db $db, $replace = false)
|
||||
{
|
||||
$properties = (array) $plain;
|
||||
$name = $properties['object_name'];
|
||||
$key = $name;
|
||||
|
||||
if ($replace && static::exists($key, $db)) {
|
||||
$object = static::load($key, $db);
|
||||
} elseif (static::exists($key, $db)) {
|
||||
throw new DuplicateKeyException(
|
||||
'Group "%s" already exists',
|
||||
$name
|
||||
);
|
||||
} else {
|
||||
$object = static::create([], $db);
|
||||
}
|
||||
|
||||
$object->setProperties($properties);
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
protected function prefersGlobalZone()
|
||||
{
|
||||
return true;
|
||||
|
|
|
@ -7,6 +7,8 @@ use Icinga\Exception\IcingaException;
|
|||
use Icinga\Module\Director\Data\PropertiesFilter;
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\Db\Cache\PrefetchCache;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
|
||||
use Icinga\Module\Director\Exception\DuplicateKeyException;
|
||||
use Icinga\Module\Director\IcingaConfig\IcingaConfig;
|
||||
use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c;
|
||||
use Icinga\Module\Director\IcingaConfig\IcingaLegacyConfigHelper as c1;
|
||||
|
@ -15,7 +17,7 @@ use Icinga\Module\Director\Resolver\HostServiceBlacklist;
|
|||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
class IcingaService extends IcingaObject
|
||||
class IcingaService extends IcingaObject implements ExportInterface
|
||||
{
|
||||
use FlappingSupport;
|
||||
|
||||
|
@ -150,6 +152,100 @@ class IcingaService extends IcingaObject
|
|||
return $this->get('use_var_overrides') === 'y';
|
||||
}
|
||||
|
||||
public function getUniqueIdentifier()
|
||||
{
|
||||
if ($this->isTemplate()) {
|
||||
return $this->getObjectName();
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
'getUniqueIdentifier() is supported by Service Templates only'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
// TODO: ksort in toPlainObject?
|
||||
$props = (array) $this->toPlainObject();
|
||||
$props['fields'] = $this->loadFieldReferences();
|
||||
ksort($props);
|
||||
|
||||
return (object) $props;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $plain
|
||||
* @param Db $db
|
||||
* @param bool $replace
|
||||
* @return IcingaService
|
||||
* @throws DuplicateKeyException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public static function import($plain, Db $db, $replace = false)
|
||||
{
|
||||
$properties = (array) $plain;
|
||||
$name = $properties['object_name'];
|
||||
if ($properties['object_type'] !== 'template') {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Can import only Templates, got "%s" for "%s"',
|
||||
$properties['object_type'],
|
||||
$name
|
||||
));
|
||||
}
|
||||
$key = [
|
||||
'object_type' => 'template',
|
||||
'object_name' => $name
|
||||
];
|
||||
|
||||
if ($replace && static::exists($key, $db)) {
|
||||
$object = static::load($key, $db);
|
||||
} elseif (static::exists($key, $db)) {
|
||||
throw new DuplicateKeyException(
|
||||
'Service Template "%s" already exists',
|
||||
$name
|
||||
);
|
||||
} else {
|
||||
$object = static::create([], $db);
|
||||
}
|
||||
|
||||
// $object->newFields = $properties['fields'];
|
||||
unset($properties['fields']);
|
||||
$object->setProperties($properties);
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
protected function loadFieldReferences()
|
||||
{
|
||||
$db = $this->getDb();
|
||||
|
||||
$res = $db->fetchAll(
|
||||
$db->select()->from([
|
||||
'sf' => 'icinga_service_field'
|
||||
], [
|
||||
'sf.datafield_id',
|
||||
'sf.is_required',
|
||||
'sf.var_filter',
|
||||
])->join(['df' => 'director_datafield'], 'df.id = sf.datafield_id', [])
|
||||
->where('service_id = ?', $this->get('id'))
|
||||
->order('varname ASC')
|
||||
);
|
||||
|
||||
if (empty($res)) {
|
||||
return [];
|
||||
} else {
|
||||
foreach ($res as $field) {
|
||||
$field->datafield_id = (int) $field->datafield_id;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return $this
|
||||
|
|
|
@ -4,11 +4,14 @@ namespace Icinga\Module\Director\Objects;
|
|||
|
||||
use Exception;
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
|
||||
use Icinga\Module\Director\Exception\DuplicateKeyException;
|
||||
use Icinga\Module\Director\IcingaConfig\IcingaConfig;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
class IcingaServiceSet extends IcingaObject
|
||||
class IcingaServiceSet extends IcingaObject implements ExportInterface
|
||||
{
|
||||
protected $table = 'icinga_service_set';
|
||||
|
||||
|
@ -110,6 +113,119 @@ class IcingaServiceSet extends IcingaObject
|
|||
return $services;
|
||||
}
|
||||
|
||||
public function getUniqueIdentifier()
|
||||
{
|
||||
return $this->getObjectName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
if ($this->get('host_id')) {
|
||||
return $this->exportSetOnHost();
|
||||
} else {
|
||||
return $this->exportTemplate();
|
||||
}
|
||||
}
|
||||
|
||||
protected function exportSetOnHost()
|
||||
{
|
||||
// TODO.
|
||||
throw new RuntimeException('Not yet');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
protected function exportTemplate()
|
||||
{
|
||||
$props = $this->getProperties();
|
||||
unset($props['id'], $props['host_id']);
|
||||
$props['services'] = [];
|
||||
foreach ($this->getServiceObjects() as $serviceObject) {
|
||||
$props['services'][$serviceObject->getObjectName()] = $serviceObject->export();
|
||||
}
|
||||
ksort($props);
|
||||
|
||||
return (object) $props;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $plain
|
||||
* @param Db $db
|
||||
* @param bool $replace
|
||||
* @return IcingaServiceSet
|
||||
* @throws DuplicateKeyException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public static function import($plain, Db $db, $replace = false)
|
||||
{
|
||||
$properties = (array) $plain;
|
||||
$name = $properties['object_name'];
|
||||
if (isset($properties['services'])) {
|
||||
$services = $properties['services'];
|
||||
unset($properties['services']);
|
||||
} else {
|
||||
$services = [];
|
||||
}
|
||||
|
||||
if ($properties['object_type'] !== 'template') {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Can import only Templates, got "%s" for "%s"',
|
||||
$properties['object_type'],
|
||||
$name
|
||||
));
|
||||
}
|
||||
if ($replace && static::exists($name, $db)) {
|
||||
$object = static::load($name, $db);
|
||||
} elseif (static::exists($name, $db)) {
|
||||
throw new DuplicateKeyException(
|
||||
'Service Set "%s" already exists',
|
||||
$name
|
||||
);
|
||||
} else {
|
||||
$object = static::create([], $db);
|
||||
}
|
||||
|
||||
$object->setProperties($properties);
|
||||
|
||||
// This is not how other imports work, but here we need an ID
|
||||
if (! $object->hasBeenLoadedFromDb()) {
|
||||
$object->store();
|
||||
}
|
||||
|
||||
$setId = $object->get('id');
|
||||
$sQuery = $db->getDbAdapter()->select()->from(
|
||||
['s' => 'icinga_service'],
|
||||
's.*'
|
||||
)->where('service_set_id = ?', $setId);
|
||||
$existingServices = IcingaService::loadAll($db, $sQuery, 'object_name');
|
||||
foreach ($services as $service) {
|
||||
if (isset($service->fields)) {
|
||||
unset($service->fields);
|
||||
}
|
||||
$name = $service->object_name;
|
||||
if (isset($existingServices[$name])) {
|
||||
$existing = $existingServices[$name];
|
||||
$existing->setProperties((array) $service);
|
||||
$existing->set('service_set_id', $setId);
|
||||
if ($existing->hasBeenModified()) {
|
||||
$existing->store();
|
||||
}
|
||||
} else {
|
||||
$new = IcingaService::create((array) $service, $db);
|
||||
$new->set('service_set_id', $setId);
|
||||
$new->store();
|
||||
}
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
public function onDelete()
|
||||
{
|
||||
$hostId = $this->get('host_id');
|
||||
|
|
|
@ -3,9 +3,12 @@
|
|||
namespace Icinga\Module\Director\Objects;
|
||||
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
|
||||
use Icinga\Module\Director\Exception\DuplicateKeyException;
|
||||
use Icinga\Module\Director\Web\Form\QuickForm;
|
||||
|
||||
class IcingaTemplateChoice extends IcingaObject
|
||||
class IcingaTemplateChoice extends IcingaObject implements ExportInterface
|
||||
{
|
||||
protected $objectTable;
|
||||
|
||||
|
@ -28,6 +31,47 @@ class IcingaTemplateChoice extends IcingaObject
|
|||
return substr(substr($this->table, 0, -16), 7);
|
||||
}
|
||||
|
||||
public function getUniqueIdentifier()
|
||||
{
|
||||
return $this->getObjectName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $plain
|
||||
* @param Db $db
|
||||
* @param bool $replace
|
||||
* @return IcingaTemplateChoice
|
||||
* @throws DuplicateKeyException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public static function import($plain, Db $db, $replace = false)
|
||||
{
|
||||
$properties = (array) $plain;
|
||||
if (isset($properties['originalId'])) {
|
||||
$id = $properties['originalId'];
|
||||
unset($properties['originalId']);
|
||||
} else {
|
||||
$id = null;
|
||||
}
|
||||
$name = $properties['object_name'];
|
||||
$key = $name;
|
||||
|
||||
if ($replace && static::exists($key, $db)) {
|
||||
$object = static::load($key, $db);
|
||||
} elseif (static::exists($key, $db)) {
|
||||
throw new DuplicateKeyException(
|
||||
'Template Choice "%s" already exists',
|
||||
$name
|
||||
);
|
||||
} else {
|
||||
$object = static::create([], $db);
|
||||
}
|
||||
|
||||
$object->setProperties($properties);
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
public function export()
|
||||
{
|
||||
$plain = (object) $this->getProperties();
|
||||
|
|
|
@ -145,7 +145,10 @@ class ImportRun extends DbObject
|
|||
public function importSource()
|
||||
{
|
||||
if ($this->importSource === null) {
|
||||
$this->importSource = ImportSource::load($this->get('source_id'), $this->connection);
|
||||
$this->importSource = ImportSource::loadWithAutoIncId(
|
||||
(int) $this->get('source_id'),
|
||||
$this->connection
|
||||
);
|
||||
}
|
||||
return $this->importSource;
|
||||
}
|
||||
|
|
|
@ -3,24 +3,27 @@
|
|||
namespace Icinga\Module\Director\Objects;
|
||||
|
||||
use Icinga\Application\Benchmark;
|
||||
use Icinga\Exception\ConfigurationError;
|
||||
use Icinga\Exception\NotFoundError;
|
||||
use Icinga\Module\Director\Data\Db\DbObjectWithSettings;
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
|
||||
use Icinga\Module\Director\Exception\DuplicateKeyException;
|
||||
use Icinga\Module\Director\Hook\PropertyModifierHook;
|
||||
use Icinga\Module\Director\Import\Import;
|
||||
use Icinga\Module\Director\Import\SyncUtils;
|
||||
use InvalidArgumentException;
|
||||
use Exception;
|
||||
|
||||
class ImportSource extends DbObjectWithSettings
|
||||
class ImportSource extends DbObjectWithSettings implements ExportInterface
|
||||
{
|
||||
protected $table = 'import_source';
|
||||
|
||||
protected $keyName = 'id';
|
||||
protected $keyName = 'source_name';
|
||||
|
||||
protected $autoincKeyName = 'id';
|
||||
|
||||
protected $protectAutoinc = false;
|
||||
|
||||
protected $defaultProperties = [
|
||||
'id' => null,
|
||||
'source_name' => null,
|
||||
|
@ -51,29 +54,44 @@ class ImportSource extends DbObjectWithSettings
|
|||
*/
|
||||
public function export()
|
||||
{
|
||||
$plain = (object) $this->getProperties();
|
||||
$plain->originalId = $plain->id;
|
||||
unset($plain->id);
|
||||
$plain = $this->getProperties();
|
||||
$plain['originalId'] = $plain['id'];
|
||||
unset($plain['id']);
|
||||
|
||||
foreach ($this->stateProperties as $key) {
|
||||
unset($plain->$key);
|
||||
unset($plain[$key]);
|
||||
}
|
||||
|
||||
$plain->settings = (object) $this->getSettings();
|
||||
$plain->modifiers = $this->exportRowModifiers();
|
||||
$plain['settings'] = (object) $this->getSettings();
|
||||
$plain['modifiers'] = $this->exportRowModifiers();
|
||||
ksort($plain);
|
||||
|
||||
return $plain;
|
||||
return (object) $plain;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $plain
|
||||
* @param Db $db
|
||||
* @param bool $replace
|
||||
* @return ImportSource
|
||||
* @throws DuplicateKeyException
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
public static function import($plain, Db $db, $replace = false)
|
||||
{
|
||||
$properties = (array) $plain;
|
||||
$id = $properties['originalId'];
|
||||
unset($properties['originalId']);
|
||||
if (isset($properties['originalId'])) {
|
||||
$id = $properties['originalId'];
|
||||
unset($properties['originalId']);
|
||||
} else {
|
||||
$id = null;
|
||||
}
|
||||
$name = $properties['source_name'];
|
||||
|
||||
if ($replace && static::existsWithNameAndId($name, $id, $db)) {
|
||||
$object = static::loadWithAutoIncId($id, $db);
|
||||
} elseif ($replace && static::exists($name, $db)) {
|
||||
$object = static::load($name, $db);
|
||||
} elseif (static::existsWithName($name, $db)) {
|
||||
throw new DuplicateKeyException(
|
||||
'Import Source %s already exists',
|
||||
|
@ -86,16 +104,36 @@ class ImportSource extends DbObjectWithSettings
|
|||
$object->newRowModifiers = $properties['modifiers'];
|
||||
unset($properties['modifiers']);
|
||||
$object->setProperties($properties);
|
||||
if ($id !== null) {
|
||||
$object->reallySet('id', $id);
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
public function getUniqueIdentifier()
|
||||
{
|
||||
return $this->get('source_name');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param Db $connection
|
||||
* @return ImportSource
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
public static function loadByName($name, Db $connection)
|
||||
{
|
||||
$db = $connection->getDbAdapter();
|
||||
$properties = $db->fetchRow(
|
||||
$db->select()->from('import_source')->where('source_name = ?', $name)
|
||||
);
|
||||
if ($properties === false) {
|
||||
throw new NotFoundError(sprintf(
|
||||
'There is no such Import Source: "%s"',
|
||||
$name
|
||||
));
|
||||
}
|
||||
|
||||
return static::create([], $connection)->setDbProperties($properties);
|
||||
}
|
||||
|
@ -111,15 +149,25 @@ class ImportSource extends DbObjectWithSettings
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param int $id
|
||||
* @param Db $connection
|
||||
* @api internal
|
||||
* @return bool
|
||||
*/
|
||||
protected static function existsWithNameAndId($name, $id, Db $connection)
|
||||
{
|
||||
$db = $connection->getDbAdapter();
|
||||
$dummy = new static;
|
||||
$idCol = $dummy->autoincKeyName;
|
||||
$keyCol = $dummy->keyName;
|
||||
|
||||
return (string) $id === (string) $db->fetchOne(
|
||||
$db->select()
|
||||
->from('import_source', 'id')
|
||||
->where('id = ?', $id)
|
||||
->where('source_name = ?', $name)
|
||||
->from($dummy->table, $idCol)
|
||||
->where("$idCol = ?", $id)
|
||||
->where("$keyCol = ?", $name)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -136,6 +184,7 @@ class ImportSource extends DbObjectWithSettings
|
|||
/**
|
||||
* @param bool $required
|
||||
* @return ImportRun|null
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
public function fetchLastRun($required = false)
|
||||
{
|
||||
|
@ -171,6 +220,7 @@ class ImportSource extends DbObjectWithSettings
|
|||
* @param $timestamp
|
||||
* @param bool $required
|
||||
* @return ImportRun|null
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
public function fetchLastRunBefore($timestamp, $required = false)
|
||||
{
|
||||
|
@ -186,7 +236,7 @@ class ImportSource extends DbObjectWithSettings
|
|||
$query = $db->select()->from(
|
||||
['ir' => 'import_run'],
|
||||
'ir.id'
|
||||
)->where('ir.source_id = ?', $this->id)
|
||||
)->where('ir.source_id = ?', $this->get('id'))
|
||||
->where('ir.start_time < ?', date('Y-m-d H:i:s', $timestamp))
|
||||
->order('ir.start_time DESC')
|
||||
->limit(1);
|
||||
|
@ -200,12 +250,17 @@ class ImportSource extends DbObjectWithSettings
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $required
|
||||
* @return null
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
protected function nullUnlessRequired($required)
|
||||
{
|
||||
if ($required) {
|
||||
throw new NotFoundError(
|
||||
'No data has been imported for "%s" yet',
|
||||
$this->source_name
|
||||
$this->get('source_name')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -267,7 +322,7 @@ class ImportSource extends DbObjectWithSettings
|
|||
|
||||
$target = $modifier->getTargetProperty($key);
|
||||
if (strpos($target, '.') !== false) {
|
||||
throw new ConfigurationError(
|
||||
throw new InvalidArgumentException(
|
||||
'Cannot set value for nested key "%s"',
|
||||
$target
|
||||
);
|
||||
|
@ -309,7 +364,7 @@ class ImportSource extends DbObjectWithSettings
|
|||
$this->getConnection(),
|
||||
$db->select()
|
||||
->from('import_row_modifier')
|
||||
->where('source_id = ?', $this->id)
|
||||
->where('source_id = ?', $this->get('id'))
|
||||
->order('priority ASC')
|
||||
);
|
||||
|
||||
|
@ -320,7 +375,7 @@ class ImportSource extends DbObjectWithSettings
|
|||
{
|
||||
$mods = [];
|
||||
foreach ($this->fetchRowModifiers() as $mod) {
|
||||
$mods[] = [$mod->property_name, $mod->getInstance()];
|
||||
$mods[] = [$mod->get('property_name'), $mod->getInstance()];
|
||||
}
|
||||
|
||||
return $mods;
|
||||
|
@ -331,11 +386,12 @@ class ImportSource extends DbObjectWithSettings
|
|||
$modifiers = [];
|
||||
|
||||
foreach ($this->fetchRowModifiers() as $mod) {
|
||||
if (! array_key_exists($mod->property_name, $modifiers)) {
|
||||
$modifiers[$mod->property_name] = [];
|
||||
$name = $mod->get('property_name');
|
||||
if (! array_key_exists($name, $modifiers)) {
|
||||
$modifiers[$name] = [];
|
||||
}
|
||||
|
||||
$modifiers[$mod->property_name][] = $mod->getInstance();
|
||||
$modifiers[$name][] = $mod->getInstance();
|
||||
}
|
||||
|
||||
$this->rowModifiers = $modifiers;
|
||||
|
@ -356,32 +412,38 @@ class ImportSource extends DbObjectWithSettings
|
|||
return array_keys($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $runImport
|
||||
* @return bool
|
||||
* @throws DuplicateKeyException
|
||||
*/
|
||||
public function checkForChanges($runImport = false)
|
||||
{
|
||||
$hadChanges = false;
|
||||
|
||||
Benchmark::measure('Starting with import ' . $this->source_name);
|
||||
$name = $this->get('source_name');
|
||||
Benchmark::measure("Starting with import $name");
|
||||
try {
|
||||
$import = new Import($this);
|
||||
$this->last_attempt = date('Y-m-d H:i:s');
|
||||
$this->set('last_attempt', date('Y-m-d H:i:s'));
|
||||
if ($import->providesChanges()) {
|
||||
Benchmark::measure('Found changes for ' . $this->source_name);
|
||||
Benchmark::measure("Found changes for $name");
|
||||
$hadChanges = true;
|
||||
$this->import_state = 'pending-changes';
|
||||
$this->set('import_state', 'pending-changes');
|
||||
|
||||
if ($runImport && $import->run()) {
|
||||
Benchmark::measure('Import succeeded for ' . $this->source_name);
|
||||
$this->import_state = 'in-sync';
|
||||
Benchmark::measure("Import succeeded for $name");
|
||||
$this->set('import_state', 'in-sync');
|
||||
}
|
||||
} else {
|
||||
$this->import_state = 'in-sync';
|
||||
$this->set('import_state', 'in-sync');
|
||||
}
|
||||
|
||||
$this->last_error_message = null;
|
||||
$this->set('last_error_message', null);
|
||||
} catch (Exception $e) {
|
||||
$this->import_state = 'failing';
|
||||
Benchmark::measure('Import failed for ' . $this->source_name);
|
||||
$this->last_error_message = $e->getMessage();
|
||||
$this->set('import_state', 'failing');
|
||||
Benchmark::measure("Import failed for $name");
|
||||
$this->set('last_error_message', $e->getMessage());
|
||||
}
|
||||
|
||||
if ($this->hasBeenModified()) {
|
||||
|
@ -391,6 +453,10 @@ class ImportSource extends DbObjectWithSettings
|
|||
return $hadChanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws DuplicateKeyException
|
||||
*/
|
||||
public function runImport()
|
||||
{
|
||||
return $this->checkForChanges(true);
|
||||
|
|
|
@ -6,19 +6,22 @@ use Icinga\Application\Benchmark;
|
|||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Module\Director\Data\Db\DbObject;
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
|
||||
use Icinga\Module\Director\Exception\DuplicateKeyException;
|
||||
use Icinga\Module\Director\Import\PurgeStrategy\PurgeStrategy;
|
||||
use Icinga\Module\Director\Import\Sync;
|
||||
use Exception;
|
||||
|
||||
class SyncRule extends DbObject
|
||||
class SyncRule extends DbObject implements ExportInterface
|
||||
{
|
||||
protected $table = 'sync_rule';
|
||||
|
||||
protected $keyName = 'id';
|
||||
protected $keyName = 'rule_name';
|
||||
|
||||
protected $autoincKeyName = 'id';
|
||||
|
||||
protected $protectAutoinc = false;
|
||||
|
||||
protected $defaultProperties = [
|
||||
'id' => null,
|
||||
'rule_name' => null,
|
||||
|
@ -58,6 +61,8 @@ class SyncRule extends DbObject
|
|||
|
||||
private $newSyncProperties;
|
||||
|
||||
private $originalId;
|
||||
|
||||
public function listInvolvedSourceIds()
|
||||
{
|
||||
if (! $this->hasBeenLoadedFromDb()) {
|
||||
|
@ -76,12 +81,16 @@ class SyncRule extends DbObject
|
|||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function fetchInvolvedImportSources()
|
||||
{
|
||||
$sources = [];
|
||||
|
||||
foreach ($this->listInvolvedSourceIds() as $sourceId) {
|
||||
$sources[$sourceId] = ImportSource::load($sourceId, $this->getConnection());
|
||||
$sources[$sourceId] = ImportSource::loadWithAutoIncId($sourceId, $this->getConnection());
|
||||
}
|
||||
|
||||
return $sources;
|
||||
|
@ -130,6 +139,11 @@ class SyncRule extends DbObject
|
|||
return $this->filter()->matches($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $apply
|
||||
* @return bool
|
||||
* @throws DuplicateKeyException
|
||||
*/
|
||||
public function checkForChanges($apply = false)
|
||||
{
|
||||
$hadChanges = false;
|
||||
|
@ -170,12 +184,17 @@ class SyncRule extends DbObject
|
|||
|
||||
/**
|
||||
* @return IcingaObject[]
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getExpectedModifications()
|
||||
{
|
||||
return $this->sync()->getExpectedModifications();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws DuplicateKeyException
|
||||
*/
|
||||
public function applyChanges()
|
||||
{
|
||||
return $this->checkForChanges(true);
|
||||
|
@ -246,16 +265,17 @@ class SyncRule extends DbObject
|
|||
|
||||
public function export()
|
||||
{
|
||||
$plain = (object) $this->getProperties();
|
||||
$plain->originalId = $plain->id;
|
||||
unset($plain->id);
|
||||
$plain = $this->getProperties();
|
||||
$plain['originalId'] = $plain['id'];
|
||||
unset($plain['id']);
|
||||
|
||||
foreach ($this->stateProperties as $key) {
|
||||
unset($plain->$key);
|
||||
unset($plain[$key]);
|
||||
}
|
||||
$plain->properties = $this->exportSyncProperties();
|
||||
$plain['properties'] = $this->exportSyncProperties();
|
||||
ksort($plain);
|
||||
|
||||
return $plain;
|
||||
return (object) $plain;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -269,15 +289,21 @@ class SyncRule extends DbObject
|
|||
public static function import($plain, Db $db, $replace = false)
|
||||
{
|
||||
$properties = (array) $plain;
|
||||
$id = $properties['originalId'];
|
||||
unset($properties['originalId']);
|
||||
if (isset($properties['originalId'])) {
|
||||
$id = $properties['originalId'];
|
||||
unset($properties['originalId']);
|
||||
} else {
|
||||
$id = null;
|
||||
}
|
||||
$name = $properties['rule_name'];
|
||||
|
||||
if ($replace && static::existsWithNameAndId($name, $id, $db)) {
|
||||
$object = static::loadWithAutoIncId($id, $db);
|
||||
} elseif ($replace && static::exists($name, $db)) {
|
||||
$object = static::load($name, $db);
|
||||
} elseif (static::existsWithName($name, $db)) {
|
||||
throw new DuplicateKeyException(
|
||||
'Import Source %s already exists',
|
||||
'Sync Rule %s already exists',
|
||||
$name
|
||||
);
|
||||
} else {
|
||||
|
@ -287,10 +313,19 @@ class SyncRule extends DbObject
|
|||
$object->newSyncProperties = $properties['properties'];
|
||||
unset($properties['properties']);
|
||||
$object->setProperties($properties);
|
||||
if ($id !== null && (int) $id !== (int) $object->get('id')) {
|
||||
$object->originalId = $object->get('id');
|
||||
$object->reallySet('id', $id);
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
public function getUniqueIdentifier()
|
||||
{
|
||||
return $this->get('rule_name');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DuplicateKeyException
|
||||
*/
|
||||
|
@ -301,9 +336,15 @@ class SyncRule extends DbObject
|
|||
$connection = $this->getConnection();
|
||||
$db = $connection->getDbAdapter();
|
||||
$myId = $this->get('id');
|
||||
if ($this->originalId === null) {
|
||||
$originalId = $myId;
|
||||
} else {
|
||||
$originalId = $this->originalId;
|
||||
$this->originalId = null;
|
||||
}
|
||||
if ($this->hasBeenLoadedFromDb()) {
|
||||
$db->delete(
|
||||
'sync_rule_property',
|
||||
'sync_property',
|
||||
$db->quoteInto('rule_id = ?', $myId)
|
||||
);
|
||||
}
|
||||
|
@ -331,6 +372,7 @@ class SyncRule extends DbObject
|
|||
unset($properties['id']);
|
||||
unset($properties['rule_id']);
|
||||
unset($properties['source_id']);
|
||||
ksort($properties);
|
||||
$all[] = (object) $properties;
|
||||
}
|
||||
|
||||
|
@ -491,8 +533,6 @@ class SyncRule extends DbObject
|
|||
}
|
||||
|
||||
/**
|
||||
* TODO: idem
|
||||
*
|
||||
* @param string $name
|
||||
* @param int $id
|
||||
* @param Db $connection
|
||||
|
@ -502,12 +542,15 @@ class SyncRule extends DbObject
|
|||
protected static function existsWithNameAndId($name, $id, Db $connection)
|
||||
{
|
||||
$db = $connection->getDbAdapter();
|
||||
$dummy = new static;
|
||||
$idCol = $dummy->autoincKeyName;
|
||||
$keyCol = $dummy->keyName;
|
||||
|
||||
return (string) $id === (string) $db->fetchOne(
|
||||
$db->select()
|
||||
->from('sync_rule', 'id')
|
||||
->where('id = ?', $id)
|
||||
->where('rule_name = ?', $name)
|
||||
->from($dummy->table, $idCol)
|
||||
->where("$idCol = ?", $id)
|
||||
->where("$keyCol = ?", $name)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -183,7 +183,8 @@ abstract class ActionController extends Controller implements ControlsAndContent
|
|||
$viewRenderer = null;
|
||||
}
|
||||
|
||||
if ($this->getRequest()->isApiRequest()) {
|
||||
$cType = $this->getResponse()->getHeader('Content-Type', true);
|
||||
if ($this->getRequest()->isApiRequest() || ($cType !== null && $cType !== 'text/html')) {
|
||||
$this->_helper->layout()->disableLayout();
|
||||
if ($viewRenderer) {
|
||||
$viewRenderer->disable();
|
||||
|
|
|
@ -6,6 +6,7 @@ use Icinga\Exception\IcingaException;
|
|||
use Icinga\Exception\InvalidPropertyException;
|
||||
use Icinga\Exception\NotFoundError;
|
||||
use Icinga\Module\Director\Deployment\DeploymentInfo;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
|
||||
use Icinga\Module\Director\Forms\DeploymentLinkForm;
|
||||
use Icinga\Module\Director\Forms\IcingaCloneObjectForm;
|
||||
use Icinga\Module\Director\Forms\IcingaObjectFieldForm;
|
||||
|
@ -79,6 +80,9 @@ abstract class ObjectController extends ActionController
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
if (! $this->getRequest()->isApiRequest()) {
|
||||
|
@ -111,6 +115,9 @@ abstract class ObjectController extends ActionController
|
|||
$this->content()->add($form);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
public function editAction()
|
||||
{
|
||||
$object = $this->requireObject();
|
||||
|
@ -118,9 +125,14 @@ abstract class ObjectController extends ActionController
|
|||
$this->addObjectTitle()
|
||||
->addObjectForm($object)
|
||||
->addActionClone()
|
||||
->addActionUsage();
|
||||
->addActionUsage()
|
||||
->addActionBasket();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFoundError
|
||||
* @throws \Icinga\Security\SecurityException
|
||||
*/
|
||||
public function renderAction()
|
||||
{
|
||||
$this->assertTypePermission()
|
||||
|
@ -133,6 +145,9 @@ abstract class ObjectController extends ActionController
|
|||
$preview->renderTo($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
public function cloneAction()
|
||||
{
|
||||
$this->assertTypePermission();
|
||||
|
@ -151,6 +166,10 @@ abstract class ObjectController extends ActionController
|
|||
->content()->add($form);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFoundError
|
||||
* @throws \Icinga\Security\SecurityException
|
||||
*/
|
||||
public function fieldsAction()
|
||||
{
|
||||
$this->assertPermission('director/admin');
|
||||
|
@ -187,6 +206,10 @@ abstract class ObjectController extends ActionController
|
|||
$table->renderTo($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFoundError
|
||||
* @throws \Icinga\Security\SecurityException
|
||||
*/
|
||||
public function historyAction()
|
||||
{
|
||||
$this
|
||||
|
@ -206,6 +229,9 @@ abstract class ObjectController extends ActionController
|
|||
->renderTo($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
public function membershipAction()
|
||||
{
|
||||
$object = $this->requireObject();
|
||||
|
@ -224,6 +250,10 @@ abstract class ObjectController extends ActionController
|
|||
->renderTo($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
protected function addObjectTitle()
|
||||
{
|
||||
$object = $this->requireObject();
|
||||
|
@ -271,6 +301,37 @@ abstract class ObjectController extends ActionController
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
protected function addActionBasket()
|
||||
{
|
||||
if ($this->hasBasketSupport()) {
|
||||
$object = $this->object;
|
||||
if ($object instanceof ExportInterface) {
|
||||
if ($object->isTemplate()) {
|
||||
$type = $this->getType() . 'Template';
|
||||
} elseif ($object->isGroup()) {
|
||||
$type = ucfirst($this->getType());
|
||||
} else {
|
||||
// Command? Sure?
|
||||
$type = ucfirst($this->getType());
|
||||
}
|
||||
$this->actions()->add(Link::create(
|
||||
$this->translate('Add to Basket'),
|
||||
'director/basket/add',
|
||||
[
|
||||
'type' => $type,
|
||||
'names' => $object->getUniqueIdentifier()
|
||||
],
|
||||
['class' => 'icon-tag']
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function addTemplate()
|
||||
{
|
||||
$this->assertPermission('director/admin');
|
||||
|
@ -450,10 +511,20 @@ abstract class ObjectController extends ActionController
|
|||
return $form;
|
||||
}
|
||||
|
||||
protected function hasBasketSupport()
|
||||
{
|
||||
return $this->object->isTemplate() || $this->object->isGroup();
|
||||
}
|
||||
|
||||
protected function onObjectFormLoaded(DirectorObjectForm $form)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IcingaObject
|
||||
* @throws NotFoundError
|
||||
* @throws \Zend_Controller_Response_Exception
|
||||
*/
|
||||
protected function requireObject()
|
||||
{
|
||||
if (! $this->object) {
|
||||
|
|
|
@ -56,7 +56,6 @@ abstract class ObjectsController extends ActionController
|
|||
|
||||
/**
|
||||
* @return IcingaObjectsHandler
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
protected function apiRequestHandler()
|
||||
|
@ -82,7 +81,6 @@ abstract class ObjectsController extends ActionController
|
|||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws \Icinga\Exception\Http\HttpNotFoundException
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
|
@ -124,7 +122,6 @@ abstract class ObjectsController extends ActionController
|
|||
|
||||
/**
|
||||
* @return ObjectsTable
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
*/
|
||||
protected function getTable()
|
||||
{
|
||||
|
@ -134,9 +131,24 @@ abstract class ObjectsController extends ActionController
|
|||
|
||||
/**
|
||||
* @throws NotFoundError
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
*/
|
||||
public function edittemplatesAction()
|
||||
{
|
||||
$this->commonForEdit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
public function editAction()
|
||||
{
|
||||
$this->commonForEdit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
public function commonForEdit()
|
||||
{
|
||||
$type = ucfirst($this->getType());
|
||||
|
||||
|
@ -293,7 +305,7 @@ abstract class ObjectsController extends ActionController
|
|||
|
||||
/**
|
||||
* @return array
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
protected function loadMultiObjectsFromParams()
|
||||
{
|
||||
|
@ -341,7 +353,6 @@ abstract class ObjectsController extends ActionController
|
|||
/**
|
||||
* @param ZfQueryBasedTable $table
|
||||
* @return ZfQueryBasedTable
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @throws NotFoundError
|
||||
*/
|
||||
protected function eventuallyFilterCommand(ZfQueryBasedTable $table)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Icinga\Module\Director\Web\Controller;
|
||||
|
||||
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
|
||||
use Icinga\Module\Director\Objects\IcingaObject;
|
||||
use Icinga\Module\Director\Web\Controller\Extension\DirectorDb;
|
||||
use Icinga\Module\Director\Web\Table\ApplyRulesTable;
|
||||
|
@ -115,6 +116,18 @@ abstract class TemplateController extends CompatController
|
|||
['class' => 'icon-edit']
|
||||
)
|
||||
]);
|
||||
if ($template instanceof ExportInterface) {
|
||||
$this->actions()->add(Link::create(
|
||||
$this->translate('Add to Basket'),
|
||||
'director/basket/add',
|
||||
[
|
||||
'type' => ucfirst($this->getType()) . 'Template',
|
||||
'names' => $template->getUniqueIdentifier()
|
||||
],
|
||||
['class' => 'icon-tag']
|
||||
));
|
||||
}
|
||||
|
||||
$list = new UnorderedList([], [
|
||||
'class' => 'vertical-action-list'
|
||||
]);
|
||||
|
|
|
@ -29,8 +29,7 @@ class ObjectPreview
|
|||
|
||||
/**
|
||||
* @param ControlsAndContent $cc
|
||||
* @throws \Icinga\Exception\IcingaException
|
||||
* @throws \Icinga\Exception\ProgrammingError
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function renderTo(ControlsAndContent $cc)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Web\Table;
|
||||
|
||||
use dipl\Html\Html;
|
||||
use dipl\Html\Link;
|
||||
use dipl\Web\Table\ZfQueryBasedTable;
|
||||
use Icinga\Date\DateFormatter;
|
||||
use Icinga\Module\Director\Core\Json;
|
||||
use Icinga\Module\Director\DirectorObject\Automation\Basket;
|
||||
use RuntimeException;
|
||||
|
||||
class BasketSnapshotTable extends ZfQueryBasedTable
|
||||
{
|
||||
protected $searchColumns = [
|
||||
'basket_name',
|
||||
'summary'
|
||||
];
|
||||
|
||||
/** @var Basket */
|
||||
protected $basket;
|
||||
|
||||
public function setBasket(Basket $basket)
|
||||
{
|
||||
$this->basket = $basket;
|
||||
$this->searchColumns = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function renderRow($row)
|
||||
{
|
||||
$this->splitByDay($row->ts_create_seconds);
|
||||
$link = $this->linkToSnapshot($this->renderSummary($row->summary), $row);
|
||||
|
||||
if ($this->basket === null) {
|
||||
$columns = [
|
||||
[
|
||||
new Link(
|
||||
Html::tag('strong', $row->basket_name),
|
||||
'director/basket',
|
||||
['name' => $row->basket_name]
|
||||
),
|
||||
Html::tag('br'),
|
||||
$link,
|
||||
],
|
||||
DateFormatter::formatTime($row->ts_create / 1000),
|
||||
];
|
||||
} else {
|
||||
$columns = [
|
||||
$link,
|
||||
DateFormatter::formatTime($row->ts_create / 1000),
|
||||
];
|
||||
}
|
||||
return $this::row($columns);
|
||||
}
|
||||
|
||||
protected function renderSummary($summary)
|
||||
{
|
||||
$summary = Json::decode($summary);
|
||||
if ($summary === null) {
|
||||
return '-';
|
||||
}
|
||||
$result = [];
|
||||
if (! is_object($summary) && ! is_array($summary)) {
|
||||
throw new RuntimeException(sprintf(
|
||||
'Got invalid basket summary: %s ',
|
||||
var_export($summary, 1)
|
||||
));
|
||||
}
|
||||
|
||||
foreach ($summary as $type => $count) {
|
||||
$result[] = sprintf(
|
||||
'%dx %s',
|
||||
$count,
|
||||
$type
|
||||
);
|
||||
}
|
||||
|
||||
if (empty($result)) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
return implode(', ', $result);
|
||||
}
|
||||
|
||||
protected function linkToSnapshot($caption, $row)
|
||||
{
|
||||
return new Link($caption, 'director/basket/snapshot', [
|
||||
'checksum' => bin2hex($row->content_checksum),
|
||||
'ts' => $row->ts_create,
|
||||
'name' => $row->basket_name,
|
||||
]);
|
||||
}
|
||||
|
||||
public function prepareQuery()
|
||||
{
|
||||
$query = $this->db()->select()->from([
|
||||
'b' => 'director_basket'
|
||||
], [
|
||||
'b.uuid',
|
||||
'b.basket_name',
|
||||
'bs.ts_create',
|
||||
'ts_create_seconds' => '(bs.ts_create / 1000)',
|
||||
'bs.content_checksum',
|
||||
'bc.summary',
|
||||
])->join(
|
||||
['bs' => 'director_basket_snapshot'],
|
||||
'bs.basket_uuid = b.uuid',
|
||||
[]
|
||||
)->join(
|
||||
['bc' => 'director_basket_content'],
|
||||
'bc.checksum = bs.content_checksum',
|
||||
[]
|
||||
)->order('bs.ts_create DESC');
|
||||
|
||||
if ($this->basket !== null) {
|
||||
$query->where('b.uuid = ?', $this->basket->get('uuid'));
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Web\Table;
|
||||
|
||||
use dipl\Html\Link;
|
||||
use dipl\Web\Table\ZfQueryBasedTable;
|
||||
|
||||
class BasketTable extends ZfQueryBasedTable
|
||||
{
|
||||
protected $searchColumns = [
|
||||
'basket_name',
|
||||
];
|
||||
|
||||
public function renderRow($row)
|
||||
{
|
||||
$hexUuid = bin2hex($row->uuid);
|
||||
$tr = $this::row([
|
||||
new Link(
|
||||
$row->basket_name,
|
||||
'director/basket',
|
||||
['name' => $row->basket_name]
|
||||
),
|
||||
$row->cnt_snapshots
|
||||
]);
|
||||
|
||||
return $tr;
|
||||
}
|
||||
|
||||
public function getColumnsToBeRendered()
|
||||
{
|
||||
return [
|
||||
$this->translate('Basket'),
|
||||
$this->translate('Snapshots'),
|
||||
];
|
||||
}
|
||||
|
||||
public function prepareQuery()
|
||||
{
|
||||
return $this->db()->select()->from([
|
||||
'b' => 'director_basket'
|
||||
], [
|
||||
'b.uuid',
|
||||
'b.basket_name',
|
||||
'cnt_snapshots' => 'COUNT(bs.basket_uuid)',
|
||||
])->joinLeft(
|
||||
['bs' => 'director_basket_snapshot'],
|
||||
'bs.basket_uuid = b.uuid',
|
||||
[]
|
||||
)->group('b.uuid');
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Icinga\Module\Director\Web\Table;
|
||||
|
||||
use dipl\Web\Table\Extension\MultiSelect;
|
||||
use Icinga\Authentication\Auth;
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Module\Director\Db;
|
||||
|
@ -17,6 +18,8 @@ use Zend_Db_Select as ZfSelect;
|
|||
|
||||
class TemplatesTable extends ZfQueryBasedTable
|
||||
{
|
||||
use MultiSelect;
|
||||
|
||||
protected $searchColumns = ['o.object_name'];
|
||||
|
||||
private $type;
|
||||
|
@ -28,6 +31,16 @@ class TemplatesTable extends ZfQueryBasedTable
|
|||
return $table;
|
||||
}
|
||||
|
||||
protected function assemble()
|
||||
{
|
||||
$type = $this->type;
|
||||
$this->enableMultiSelect(
|
||||
"director/${type}s/edittemplates",
|
||||
"director/${type}template",
|
||||
['name']
|
||||
);
|
||||
}
|
||||
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
ALTER TABLE import_source
|
||||
ADD UNIQUE INDEX source_name (source_name);
|
||||
|
||||
ALTER TABLE sync_rule
|
||||
ADD UNIQUE INDEX rule_name (rule_name);
|
||||
|
||||
INSERT INTO director_schema_migration
|
||||
(schema_version, migration_time)
|
||||
VALUES (152, NOW());
|
|
@ -0,0 +1,42 @@
|
|||
CREATE TABLE director_basket (
|
||||
uuid VARBINARY(16) NOT NULL,
|
||||
basket_name VARCHAR(64) NOT NULL,
|
||||
owner_type ENUM(
|
||||
'user',
|
||||
'usergroup',
|
||||
'role'
|
||||
) NOT NULL,
|
||||
owner_value VARCHAR(255) NOT NULL,
|
||||
objects MEDIUMTEXT NOT NULL, -- json-encoded
|
||||
PRIMARY KEY (uuid),
|
||||
UNIQUE INDEX basket_name (basket_name)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_bin;
|
||||
|
||||
CREATE TABLE director_basket_content (
|
||||
checksum VARBINARY(20) NOT NULL,
|
||||
summary VARCHAR(255) NOT NULL, -- json
|
||||
content MEDIUMTEXT NOT NULL, -- json
|
||||
PRIMARY KEY (checksum)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_bin;
|
||||
|
||||
CREATE TABLE director_basket_snapshot (
|
||||
basket_uuid VARBINARY(16) NOT NULL,
|
||||
ts_create BIGINT(20) NOT NULL,
|
||||
content_checksum VARBINARY(20) NOT NULL,
|
||||
PRIMARY KEY (basket_uuid, ts_create),
|
||||
INDEX sort_idx (ts_create),
|
||||
CONSTRAINT basked_snapshot_basket
|
||||
FOREIGN KEY director_basket_snapshot (basket_uuid)
|
||||
REFERENCES director_basket (uuid)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE RESTRICT,
|
||||
CONSTRAINT basked_snapshot_content
|
||||
FOREIGN KEY content_checksum (content_checksum)
|
||||
REFERENCES director_basket_content (checksum)
|
||||
ON DELETE RESTRICT
|
||||
ON UPDATE RESTRICT
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_bin;
|
||||
|
||||
INSERT INTO director_schema_migration
|
||||
(schema_version, migration_time)
|
||||
VALUES (153, NOW());
|
|
@ -28,6 +28,45 @@ CREATE TABLE director_activity_log (
|
|||
INDEX checksum (checksum)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE director_basket (
|
||||
uuid VARBINARY(16) NOT NULL,
|
||||
basket_name VARCHAR(64) NOT NULL,
|
||||
owner_type ENUM(
|
||||
'user',
|
||||
'usergroup',
|
||||
'role'
|
||||
) NOT NULL,
|
||||
owner_value VARCHAR(255) NOT NULL,
|
||||
objects MEDIUMTEXT NOT NULL, -- json-encoded
|
||||
PRIMARY KEY (uuid),
|
||||
UNIQUE INDEX basket_name (basket_name)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_bin;
|
||||
|
||||
CREATE TABLE director_basket_content (
|
||||
checksum VARBINARY(20) NOT NULL,
|
||||
summary VARCHAR(255) NOT NULL, -- json
|
||||
content MEDIUMTEXT NOT NULL, -- json
|
||||
PRIMARY KEY (checksum)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_bin;
|
||||
|
||||
CREATE TABLE director_basket_snapshot (
|
||||
basket_uuid VARBINARY(16) NOT NULL,
|
||||
ts_create BIGINT(20) NOT NULL,
|
||||
content_checksum VARBINARY(20) NOT NULL,
|
||||
PRIMARY KEY (basket_uuid, ts_create),
|
||||
INDEX sort_idx (ts_create),
|
||||
CONSTRAINT basked_snapshot_basket
|
||||
FOREIGN KEY director_basket_snapshot (basket_uuid)
|
||||
REFERENCES director_basket (uuid)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE RESTRICT,
|
||||
CONSTRAINT basked_snapshot_content
|
||||
FOREIGN KEY content_checksum (content_checksum)
|
||||
REFERENCES director_basket_content (checksum)
|
||||
ON DELETE RESTRICT
|
||||
ON UPDATE RESTRICT
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_bin;
|
||||
|
||||
CREATE TABLE director_generated_config (
|
||||
checksum VARBINARY(20) NOT NULL COMMENT 'SHA1(last_activity_checksum;file_path=checksum;file_path=checksum;...)',
|
||||
director_version VARCHAR(64) DEFAULT NULL,
|
||||
|
@ -1296,6 +1335,7 @@ CREATE TABLE import_source (
|
|||
last_attempt DATETIME DEFAULT NULL,
|
||||
description TEXT DEFAULT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE INDEX source_name (source_name),
|
||||
INDEX search_idx (key_column)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
|
@ -1441,7 +1481,8 @@ CREATE TABLE sync_rule (
|
|||
last_error_message TEXT DEFAULT NULL,
|
||||
last_attempt DATETIME DEFAULT NULL,
|
||||
description TEXT DEFAULT NULL,
|
||||
PRIMARY KEY (id)
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE INDEX rule_name (rule_name)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE sync_property (
|
||||
|
@ -1730,4 +1771,4 @@ CREATE TABLE icinga_timeperiod_exclude (
|
|||
|
||||
INSERT INTO director_schema_migration
|
||||
(schema_version, migration_time)
|
||||
VALUES (151, NOW());
|
||||
VALUES (153, NOW());
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
CREATE UNIQUE INDEX import_source_name ON import_source (source_name);
|
||||
|
||||
CREATE UNIQUE INDEX sync_rule_name ON sync_rule (rule_name);
|
||||
|
||||
INSERT INTO director_schema_migration
|
||||
(schema_version, migration_time)
|
||||
VALUES (152, NOW());
|
|
@ -0,0 +1,45 @@
|
|||
CREATE TYPE enum_owner_type AS ENUM('user', 'usergroup', 'role');
|
||||
|
||||
CREATE TABLE director_basket (
|
||||
uuid bytea CHECK(LENGTH(uuid) = 16) NOT NULL,
|
||||
basket_name VARCHAR(64) NOT NULL,
|
||||
owner_type enum_owner_type NOT NULL,
|
||||
owner_value VARCHAR(255) NOT NULL,
|
||||
objects text NOT NULL, -- json-encoded
|
||||
PRIMARY KEY (uuid)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX basket_basket_name ON director_basket (basket_name);
|
||||
|
||||
|
||||
CREATE TABLE director_basket_content (
|
||||
checksum bytea CHECK(LENGTH(checksum) = 20) NOT NULL,
|
||||
summary VARCHAR(255) NOT NULL, -- json
|
||||
content text NOT NULL, -- json
|
||||
PRIMARY KEY (checksum)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE director_basket_snapshot (
|
||||
basket_uuid bytea CHECK(LENGTH(basket_uuid) = 16) NOT NULL,
|
||||
ts_create bigint NOT NULL,
|
||||
content_checksum bytea CHECK(LENGTH(content_checksum) = 20) NOT NULL,
|
||||
PRIMARY KEY (basket_uuid, ts_create),
|
||||
CONSTRAINT basked_snapshot_basket
|
||||
FOREIGN KEY (basket_uuid)
|
||||
REFERENCES director_basket (uuid)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE RESTRICT,
|
||||
CONSTRAINT basked_snapshot_content
|
||||
FOREIGN KEY (content_checksum)
|
||||
REFERENCES director_basket_content (checksum)
|
||||
ON DELETE RESTRICT
|
||||
ON UPDATE RESTRICT
|
||||
);
|
||||
|
||||
CREATE INDEX basket_snapshot_sort_idx ON director_basket_snapshot (ts_create);
|
||||
|
||||
|
||||
INSERT INTO director_schema_migration
|
||||
(schema_version, migration_time)
|
||||
VALUES (153, NOW());
|
|
@ -42,6 +42,7 @@ CREATE TYPE enum_sync_state AS ENUM(
|
|||
'failing'
|
||||
);
|
||||
CREATE TYPE enum_host_service AS ENUM('host', 'service');
|
||||
CREATE TYPE enum_owner_type AS ENUM('user', 'usergroup', 'role');
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION unix_timestamp(timestamp with time zone) RETURNS bigint AS '
|
||||
|
@ -71,6 +72,46 @@ COMMENT ON COLUMN director_activity_log.old_properties IS 'Property hash, JSON';
|
|||
COMMENT ON COLUMN director_activity_log.new_properties IS 'Property hash, JSON';
|
||||
|
||||
|
||||
CREATE TABLE director_basket (
|
||||
uuid bytea CHECK(LENGTH(uuid) = 16) NOT NULL,
|
||||
basket_name VARCHAR(64) NOT NULL,
|
||||
owner_type enum_owner_type NOT NULL,
|
||||
owner_value VARCHAR(255) NOT NULL,
|
||||
objects text NOT NULL, -- json-encoded
|
||||
PRIMARY KEY (uuid)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX basket_basket_name ON director_basket (basket_name);
|
||||
|
||||
|
||||
CREATE TABLE director_basket_content (
|
||||
checksum bytea CHECK(LENGTH(checksum) = 20) NOT NULL,
|
||||
summary VARCHAR(255) NOT NULL, -- json
|
||||
content text NOT NULL, -- json
|
||||
PRIMARY KEY (checksum)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE director_basket_snapshot (
|
||||
basket_uuid bytea CHECK(LENGTH(basket_uuid) = 16) NOT NULL,
|
||||
ts_create bigint NOT NULL,
|
||||
content_checksum bytea CHECK(LENGTH(content_checksum) = 20) NOT NULL,
|
||||
PRIMARY KEY (basket_uuid, ts_create),
|
||||
CONSTRAINT basked_snapshot_basket
|
||||
FOREIGN KEY (basket_uuid)
|
||||
REFERENCES director_basket (uuid)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE RESTRICT,
|
||||
CONSTRAINT basked_snapshot_content
|
||||
FOREIGN KEY (content_checksum)
|
||||
REFERENCES director_basket_content (checksum)
|
||||
ON DELETE RESTRICT
|
||||
ON UPDATE RESTRICT
|
||||
);
|
||||
|
||||
CREATE INDEX basket_snapshot_sort_idx ON director_basket_snapshot (ts_create);
|
||||
|
||||
|
||||
CREATE TABLE director_generated_config (
|
||||
checksum bytea CHECK(LENGTH(checksum) = 20),
|
||||
director_version character varying(64) DEFAULT NULL,
|
||||
|
@ -1439,6 +1480,7 @@ CREATE TABLE import_source (
|
|||
);
|
||||
|
||||
CREATE INDEX import_source_search_idx ON import_source (key_column);
|
||||
CREATE UNIQUE INDEX import_source_name ON import_source (source_name);
|
||||
|
||||
|
||||
CREATE TABLE import_source_setting (
|
||||
|
@ -1594,6 +1636,7 @@ CREATE TABLE sync_rule (
|
|||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX sync_rule_name ON sync_rule (rule_name);
|
||||
|
||||
CREATE TABLE sync_property (
|
||||
id serial,
|
||||
|
@ -2025,4 +2068,4 @@ CREATE TABLE icinga_timeperiod_exclude (
|
|||
|
||||
INSERT INTO director_schema_migration
|
||||
(schema_version, migration_time)
|
||||
VALUES (151, NOW());
|
||||
VALUES (153, NOW());
|
||||
|
|
Loading…
Reference in New Issue