BasketSnapshotFieldResolver: deal with fields
Also, this fixes Service Sets
This commit is contained in:
parent
9ed1503ad6
commit
83dc9dc6c3
|
@ -11,6 +11,7 @@ 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\BasketCreateSnapshotForm;
|
||||
use Icinga\Module\Director\Forms\BasketForm;
|
||||
use Icinga\Module\Director\Forms\RestoreBasketForm;
|
||||
|
@ -38,6 +39,7 @@ class BasketController extends ActionController
|
|||
|
||||
/**
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
* @throws \Icinga\Exception\MissingParameterException
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
|
@ -188,8 +190,11 @@ class BasketController extends ActionController
|
|||
$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;
|
||||
}
|
||||
|
@ -219,7 +224,9 @@ class BasketController extends ActionController
|
|||
);
|
||||
continue;
|
||||
}
|
||||
$hasChanged = Json::encode($current->export()) !== Json::encode($object);
|
||||
$currentExport = $current->export();
|
||||
$fieldResolver->tweakTargetIds($currentExport);
|
||||
$hasChanged = Json::encode($currentExport) !== Json::encode($object);
|
||||
$table->addNameValueRow(
|
||||
$key,
|
||||
$hasChanged
|
||||
|
@ -296,12 +303,15 @@ class BasketController extends ActionController
|
|||
} 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 {
|
||||
$current = Json::encode($current->export(), JSON_PRETTY_PRINT);
|
||||
$exported = $current->export();
|
||||
$fieldResolver->tweakTargetIds($exported);
|
||||
$current = Json::encode($exported, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
$this->content()->add(
|
||||
|
@ -312,6 +322,11 @@ class BasketController extends ActionController
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Basket
|
||||
* @throws \Icinga\Exception\MissingParameterException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
protected function requireBasket()
|
||||
{
|
||||
return Basket::load($this->params->getRequired('name'), $this->db());
|
||||
|
|
|
@ -65,6 +65,9 @@ class RestoreBasketForm extends QuickForm
|
|||
return Db::fromResourceName($this->getValue('target_db'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public function onSuccess()
|
||||
{
|
||||
$this->snapshot->restoreTo($this->getDb());
|
||||
|
|
|
@ -25,7 +25,6 @@ class BasketSnapshot extends DbObject
|
|||
];
|
||||
|
||||
protected $restoreOrder = [
|
||||
'Datafield',
|
||||
'Command',
|
||||
'HostGroup',
|
||||
'IcingaTemplateChoiceHost',
|
||||
|
@ -93,30 +92,17 @@ class BasketSnapshot extends DbObject
|
|||
*/
|
||||
protected function resolveRequiredFields()
|
||||
{
|
||||
$requiredIds = [];
|
||||
foreach ($this->objects as $typeName => $objects) {
|
||||
foreach ($objects as $key => $object) {
|
||||
if (isset($object->fields)) {
|
||||
foreach ($object->fields as $field) {
|
||||
$requiredIds[$field->datafield_id] = true;
|
||||
}
|
||||
}
|
||||
/** @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();
|
||||
}
|
||||
}
|
||||
|
||||
$connection = $this->getConnection();
|
||||
if (! isset($this->objects['Datafield'])) {
|
||||
$this->objects['Datafield'] = [];
|
||||
}
|
||||
$fields = & $this->objects['Datafield'];
|
||||
foreach (array_keys($requiredIds) as $id) {
|
||||
if (! isset($fields[$id])) {
|
||||
$fields[$id] = DirectorDatafield::loadWithAutoIncId((int) $id, $connection)->export();
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($this->objects['Datafield'])) {
|
||||
unset($this->objects['Datafield']);
|
||||
$this->objects['Datafield'] = $plain;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,9 +143,7 @@ class BasketSnapshot extends DbObject
|
|||
/**
|
||||
* @param Db $connection
|
||||
* @param bool $replace
|
||||
* @throws \Icinga\Module\Director\Exception\DuplicateKeyException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
* @throws \Zend_Db_Adapter_Exception
|
||||
*/
|
||||
public function restoreTo(Db $connection, $replace = true)
|
||||
{
|
||||
|
@ -186,12 +170,14 @@ class BasketSnapshot extends DbObject
|
|||
* @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();
|
||||
$fieldMap = [];
|
||||
$fieldResolver = new BasketSnapshotFieldResolver($all, $connection);
|
||||
$fieldResolver->storeNewFields();
|
||||
foreach ($this->restoreOrder as $typeName) {
|
||||
if (isset($all->$typeName)) {
|
||||
$objects = $all->$typeName;
|
||||
|
@ -208,12 +194,9 @@ class BasketSnapshot extends DbObject
|
|||
$new->store();
|
||||
}
|
||||
}
|
||||
if ($new instanceof DirectorDatafield) {
|
||||
$fieldMap[(int) $key] = (int) $new->get('id');
|
||||
}
|
||||
|
||||
if ($new instanceof IcingaObject) {
|
||||
$this->relinkObjectFields($db, $new, $object, $fieldMap);
|
||||
$fieldResolver->relinkObjectFields($new, $object);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,53 +209,6 @@ class BasketSnapshot extends DbObject
|
|||
$db->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZfDbAdapter $db
|
||||
* @param IcingaObject $new
|
||||
* @param $object
|
||||
* @param $fieldMap
|
||||
* @throws \Zend_Db_Adapter_Exception
|
||||
*/
|
||||
protected function relinkObjectFields(ZfDbAdapter $db, IcingaObject $new, $object, $fieldMap)
|
||||
{
|
||||
if (! $new->supportsFields() || ! isset($object->fields)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$objectId = (int) $new->get('id');
|
||||
$table = $new->getTableName() . '_field';
|
||||
$objectKey = $new->getShortTableName() . '_id';
|
||||
$existingFields = [];
|
||||
|
||||
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 IcingaObject $object
|
||||
* @param $list
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -80,7 +80,6 @@ class DirectorDatafield extends DbObjectWithSettings
|
|||
* @param Db $db
|
||||
* @param bool $replace
|
||||
* @return DirectorDatafield
|
||||
* @throws DuplicateKeyException
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
public static function import($plain, Db $db, $replace = false)
|
||||
|
@ -93,6 +92,15 @@ class DirectorDatafield extends DbObjectWithSettings
|
|||
$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)) {
|
||||
|
|
|
@ -339,6 +339,9 @@ class IcingaHost extends IcingaObject
|
|||
if (empty($res)) {
|
||||
return [];
|
||||
} else {
|
||||
foreach ($res as $field) {
|
||||
$field->datafield_id = (int) $field->datafield_id;
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -237,6 +237,10 @@ class IcingaService extends IcingaObject
|
|||
if (empty($res)) {
|
||||
return [];
|
||||
} else {
|
||||
foreach ($res as $field) {
|
||||
$field->datafield_id = (int) $field->datafield_id;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +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 Icinga\Module\Director\Web\Form\IcingaObjectFieldLoader;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
class IcingaServiceSet extends IcingaObject
|
||||
class IcingaServiceSet extends IcingaObject implements ExportInterface
|
||||
{
|
||||
protected $table = 'icinga_service_set';
|
||||
|
||||
|
@ -111,6 +113,15 @@ 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')) {
|
||||
|
@ -123,27 +134,98 @@ class IcingaServiceSet extends IcingaObject
|
|||
protected function exportSetOnHost()
|
||||
{
|
||||
// TODO.
|
||||
throw new \RuntimeException('Not yet');
|
||||
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'] = [];
|
||||
$props['service_templates'] = [];
|
||||
foreach ($this->getServiceObjects() as $serviceObject) {
|
||||
$props['services'][$serviceObject->getObjectName()] = $serviceObject->export();
|
||||
foreach ($serviceObject->imports()->getObjects() as $import) {
|
||||
$name = $import->getObjectName();
|
||||
$props['service_templates'][$name] = $import->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');
|
||||
|
|
Loading…
Reference in New Issue