parent
5d309b3dc7
commit
f4220016d8
|
@ -0,0 +1,285 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Controllers;
|
||||
|
||||
use dipl\Html\Link;
|
||||
use dipl\Web\Widget\NameValueTable;
|
||||
use Exception;
|
||||
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\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()
|
||||
{
|
||||
$uuid = $this->params->get('uuid');
|
||||
return $this->tabs()->add('show', [
|
||||
'label' => $this->translate('Basket'),
|
||||
'url' => 'director/basket',
|
||||
'urlParams' => ['uuid' => $uuid]
|
||||
])->add('snapshots', [
|
||||
'label' => $this->translate('Snapshots'),
|
||||
'url' => 'director/basket/snapshots',
|
||||
'urlParams' => ['uuid' => $uuid]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
$this->actions()->add(
|
||||
Link::create(
|
||||
$this->translate('Back'),
|
||||
'director/baskets',
|
||||
null,
|
||||
[
|
||||
'class' => 'icon-left-big'
|
||||
]
|
||||
)
|
||||
);
|
||||
$uuid = hex2bin($this->params->get('uuid'));
|
||||
$basket = Basket::load($uuid, $this->db());
|
||||
$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 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);
|
||||
}
|
||||
|
||||
public function snapshotsAction()
|
||||
{
|
||||
$uuid = $this->params->get('uuid');
|
||||
if ($uuid === null || $uuid === '') {
|
||||
$basket = null;
|
||||
} else {
|
||||
$uuid = hex2bin($uuid);
|
||||
$basket = Basket::load($uuid, $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()
|
||||
{
|
||||
$hexUuid = $this->params->getRequired('uuid');
|
||||
$binUuid = hex2bin($hexUuid);
|
||||
$basket = Basket::load($binUuid, $this->db());
|
||||
$snapshot = BasketSnapshot::load([
|
||||
'basket_uuid' => $binUuid,
|
||||
'ts_create' => $this->params->getRequired('ts'),
|
||||
], $this->db());
|
||||
|
||||
$this->addTitle(
|
||||
$this->translate('%s: %s (Snapshot)'),
|
||||
$basket->get('basket_name'),
|
||||
substr($hexUuid, 0, 7)
|
||||
);
|
||||
|
||||
$this->actions()->add([
|
||||
Link::create(
|
||||
$this->translate('Show Basket'),
|
||||
'director/basket',
|
||||
['uuid' => $hexUuid],
|
||||
['data-base-target' => '_next']
|
||||
),
|
||||
Link::create(
|
||||
$this->translate('Restore'),
|
||||
$this->url()->with('action', 'restore'),
|
||||
null,
|
||||
['class' => 'icon-rewind']
|
||||
)
|
||||
] );
|
||||
|
||||
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);
|
||||
foreach ($all as $type => $objects) {
|
||||
$table = new NameValueTable();
|
||||
$table->setAttribute('data-base-target', '_next');
|
||||
foreach ($objects as $key => $object) {
|
||||
$linkParams = [
|
||||
'uuid' => $hexUuid,
|
||||
'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;
|
||||
}
|
||||
$hasChanged = Json::encode($current->export()) !== 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\MissingParameterException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function snapshotobjectAction()
|
||||
{
|
||||
$hexUuid = $this->params->getRequired('uuid');
|
||||
$binUuid = hex2bin($hexUuid);
|
||||
$snapshot = BasketSnapshot::load([
|
||||
'basket_uuid' => $binUuid,
|
||||
'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($hexUuid, 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);
|
||||
}
|
||||
$object = $objects->$type->$key;
|
||||
$current = BasketSnapshot::instanceByIdentifier($type, $key, $connection);
|
||||
$this->content()->add(
|
||||
ConfigDiff::create(
|
||||
Json::encode($object, JSON_PRETTY_PRINT),
|
||||
Json::encode($current->export(), JSON_PRETTY_PRINT)
|
||||
)->setHtmlRenderer('Inline')
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,132 @@
|
|||
<?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 [
|
||||
'ImportSource' => $this->translate('Import Sources'),
|
||||
'SyncRule' => $this->translate('Sync Rules'),
|
||||
'Job' => $this->translate('Job Definitions'),
|
||||
'HostTemplate' => $this->translate('Host Templates'),
|
||||
'ServiceSet' => $this->translate('Service Sets'),
|
||||
'ServiceTemplate' => $this->translate('Service Templates'),
|
||||
'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',
|
||||
['uuid' => $basket->getHexUuid()]
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\DirectorObject\Automation;
|
||||
|
||||
use Icinga\Module\Director\Core\Json;
|
||||
use Icinga\Module\Director\Data\Db\DbObject;
|
||||
|
||||
/**
|
||||
* 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 = 'uuid';
|
||||
|
||||
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 = Json::decode($this->get('objects'));
|
||||
}
|
||||
|
||||
public function setObjects($objects)
|
||||
{
|
||||
if (empty($objects)) {
|
||||
$this->chosenObjects = [];
|
||||
} else {
|
||||
$this->chosenObjects = [];
|
||||
foreach ((array) $objects as $type => $object) {
|
||||
$this->addObjects($type, $object);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $type
|
||||
* @param ExportInterface[]|bool $objects
|
||||
*/
|
||||
public function addObjects($type, $objects = true)
|
||||
{
|
||||
// '1' -> from Form!
|
||||
if ($objects === 'ALL') {
|
||||
$objects = true;
|
||||
} elseif ($objects === null || $objects === 'IGNORE') {
|
||||
return;
|
||||
} elseif ($objects === '[]') {
|
||||
$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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $type
|
||||
* @param string $object
|
||||
*/
|
||||
public function addObject($type, $object)
|
||||
{
|
||||
// TODO: make sure array exists - and is not boolean
|
||||
$this->chosenObjects[$type][] = $object;
|
||||
}
|
||||
|
||||
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,229 @@
|
|||
<?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\IcingaObject;
|
||||
use RuntimeException;
|
||||
|
||||
class BasketSnapshot extends DbObject
|
||||
{
|
||||
protected $objects = [];
|
||||
|
||||
protected $content;
|
||||
|
||||
protected $table = 'director_basket_snapshot';
|
||||
|
||||
protected $keyName = [
|
||||
'basket_uuid',
|
||||
'ts_create',
|
||||
];
|
||||
|
||||
protected $defaultProperties = [
|
||||
'basket_uuid' => null,
|
||||
'content_checksum' => null,
|
||||
'ts_create' => null,
|
||||
];
|
||||
|
||||
public static function getClassForType($type)
|
||||
{
|
||||
$types = [
|
||||
'ImportSource' => '\\Icinga\\Module\\Director\\Objects\\ImportSource',
|
||||
'SyncRule' => '\\Icinga\\Module\\Director\\Objects\\SyncRule',
|
||||
'DirectorJob' => '\\Icinga\\Module\\Director\\Objects\\DirectorJob',
|
||||
'ServiceSet' => '\\Icinga\\Module\\Director\\Objects\\IcingaServiceSet',
|
||||
'HostTemplate' => '\\Icinga\\Module\\Director\\Objects\\IcingaHost',
|
||||
'ServiceTemplate' => '\\Icinga\\Module\\Director\\Objects\\IcingaService',
|
||||
'Basket' => '\\Icinga\\Module\\Director\\DirectorObject\\Automation\\Automation',
|
||||
];
|
||||
|
||||
return $types[$type];
|
||||
}
|
||||
|
||||
public static function createForBasket(Basket $basket, Db $db)
|
||||
{
|
||||
$snapshot = static::create([
|
||||
'basket_uuid' => $basket->get('uuid')
|
||||
], $db);
|
||||
$snapshot->addObjectsChosenByBasket($basket);
|
||||
|
||||
return $snapshot;
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
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\Module\Director\Exception\DuplicateKeyException
|
||||
*/
|
||||
public function restoreTo(Db $connection, $replace = true)
|
||||
{
|
||||
$all = Json::decode($this->getJsonDump());
|
||||
$db = $connection->getDbAdapter();
|
||||
$db->beginTransaction();
|
||||
foreach ($all as $typeName => $objects) {
|
||||
$class = static::getClassForType($typeName);
|
||||
foreach ($objects as $object) {
|
||||
/** @var DbObject $new */
|
||||
$new = $class::import($object, $connection, $replace);
|
||||
if ($new->hasBeenModified()) {
|
||||
$new->store();
|
||||
}
|
||||
}
|
||||
}
|
||||
$db->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)'
|
||||
);
|
||||
}
|
||||
|
||||
public function getJsonSummary()
|
||||
{
|
||||
if ($this->hasBeenLoadedFromDb()) {
|
||||
return $this->getContent()->get('summary');
|
||||
} else {
|
||||
return Json::encode($this->getSummary(), JSON_PRETTY_PRINT);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public function getJsonDump()
|
||||
{
|
||||
if ($this->hasBeenLoadedFromDb()) {
|
||||
return $this->getContent()->get('content');
|
||||
} else {
|
||||
return Json::encode($this->objects, JSON_PRETTY_PRINT);
|
||||
}
|
||||
}
|
||||
|
||||
protected static function classWantsTemplate($class)
|
||||
{
|
||||
return strpos($class, '\\Icinga\\Module\\Director\\Objects\\Icinga') === 0;
|
||||
}
|
||||
|
||||
protected function addAll($typeName)
|
||||
{
|
||||
$class = static::getClassForType($typeName);
|
||||
/** @var ExportInterface $object */
|
||||
if (static::classWantsTemplate($class)) {
|
||||
/** @var IcingaObject $dummy */
|
||||
$dummy = $class::create();
|
||||
$db = $this->getDb();
|
||||
$select = $db->select()->from($dummy->getTableName())
|
||||
->where('object_type = ?', 'template');
|
||||
$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
|
||||
* @return ExportInterface
|
||||
*/
|
||||
public static function instanceByIdentifier($typeName, $identifier, Db $connection)
|
||||
{
|
||||
$class = static::getClassForType($typeName);
|
||||
if (static::classWantsTemplate($class)) {
|
||||
$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;
|
||||
}
|
||||
|
||||
protected function addByIdentifier($typeName, $identifier)
|
||||
{
|
||||
$object = static::instanceByIdentifier(
|
||||
$typeName,
|
||||
$identifier,
|
||||
$this->getConnection()
|
||||
);
|
||||
$this->objects[$typeName][$identifier] = $object->export();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Web\Table;
|
||||
|
||||
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',
|
||||
];
|
||||
|
||||
/** @var Basket */
|
||||
protected $basket;
|
||||
|
||||
public function setBasket(Basket $basket)
|
||||
{
|
||||
$this->basket = $basket;
|
||||
$this->searchColumns = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function renderRow($row)
|
||||
{
|
||||
$hexUuid = bin2hex($row->uuid);
|
||||
$link = $this->linkToSnapshot($this->renderSummary($row->summary), $row);
|
||||
|
||||
if ($this->basket === null) {
|
||||
$columns = [
|
||||
$link,
|
||||
new Link(
|
||||
$row->basket_name,
|
||||
'director/basket',
|
||||
['uuid' => $hexUuid]
|
||||
),
|
||||
DateFormatter::formatDateTime($row->ts_create / 1000),
|
||||
];
|
||||
} else {
|
||||
$columns = [
|
||||
$link,
|
||||
DateFormatter::formatDateTime($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
|
||||
);
|
||||
}
|
||||
|
||||
return implode(', ', $result);
|
||||
}
|
||||
|
||||
protected function linkToSnapshot($caption, $row)
|
||||
{
|
||||
return new Link($caption, 'director/basket/snapshot', [
|
||||
'checksum' => bin2hex($row->content_checksum),
|
||||
'ts' => $row->ts_create,
|
||||
'uuid' => bin2hex($row->uuid),
|
||||
]);
|
||||
}
|
||||
|
||||
public function getColumnsToBeRendered()
|
||||
{
|
||||
if ($this->basket === null) {
|
||||
return [
|
||||
$this->translate('Content'),
|
||||
$this->translate('Basket'),
|
||||
$this->translate('Created'),
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
$this->translate('Content'),
|
||||
$this->translate('Created'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function prepareQuery()
|
||||
{
|
||||
$query = $this->db()->select()->from([
|
||||
'b' => 'director_basket'
|
||||
], [
|
||||
'b.uuid',
|
||||
'b.basket_name',
|
||||
'bs.ts_create',
|
||||
'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',
|
||||
['uuid' => $hexUuid]
|
||||
),
|
||||
$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');
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue