mirror of
https://github.com/Icinga/icingaweb2-module-director.git
synced 2025-07-31 01:34:12 +02:00
CompareBasketObject: new helper for basket diffs
...with lot's of workarounds for foreign baskets fixes #2223
This commit is contained in:
parent
a4e2ba5fc7
commit
a761485b96
@ -13,6 +13,7 @@ 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\DirectorObject\Automation\CompareBasketObject;
|
||||
use Icinga\Module\Director\Forms\AddToBasketForm;
|
||||
use Icinga\Module\Director\Forms\BasketCreateSnapshotForm;
|
||||
use Icinga\Module\Director\Forms\BasketForm;
|
||||
@ -289,7 +290,7 @@ class BasketController extends ActionController
|
||||
if (isset($object->originalId)) {
|
||||
unset($object->originalId);
|
||||
}
|
||||
$hasChanged = Json::encode($currentExport) !== Json::encode($object);
|
||||
$hasChanged = ! CompareBasketObject::equals($currentExport, $object);
|
||||
$table->addNameValueRow(
|
||||
$key,
|
||||
$hasChanged
|
||||
@ -303,7 +304,12 @@ class BasketController extends ActionController
|
||||
} catch (Exception $e) {
|
||||
$table->addNameValueRow(
|
||||
$key,
|
||||
$e->getMessage()
|
||||
Html::tag('a', sprintf(
|
||||
'%s (%s:%d)',
|
||||
$e->getMessage(),
|
||||
basename($e->getFile()),
|
||||
$e->getLine()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -368,21 +374,30 @@ class BasketController extends ActionController
|
||||
}
|
||||
$fieldResolver = new BasketSnapshotFieldResolver($objects, $connection);
|
||||
$objectFromBasket = $objects->$type->$key;
|
||||
unset($objectFromBasket->originalId);
|
||||
CompareBasketObject::normalize($objectFromBasket);
|
||||
$objectFromBasket = Json::encode($objectFromBasket, JSON_PRETTY_PRINT);
|
||||
$current = BasketSnapshot::instanceByIdentifier($type, $key, $connection);
|
||||
if ($current === null) {
|
||||
$current = '';
|
||||
} else {
|
||||
$exported = $current->export();
|
||||
$fieldResolver->tweakTargetIds($exported);
|
||||
unset($exported->originalId);
|
||||
CompareBasketObject::normalize($exported);
|
||||
$current = Json::encode($exported, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
$this->content()->add(
|
||||
ConfigDiff::create(
|
||||
$current,
|
||||
Json::encode($objectFromBasket, JSON_PRETTY_PRINT)
|
||||
)->setHtmlRenderer('Inline')
|
||||
);
|
||||
if ($current === $objectFromBasket) {
|
||||
$this->content()->add([
|
||||
Hint::ok('Basket equals current object'),
|
||||
Html::tag('pre', $current)
|
||||
]);
|
||||
} else {
|
||||
$this->content()->add(
|
||||
ConfigDiff::create($current, $objectFromBasket)->setHtmlRenderer('Inline')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\DirectorObject\Automation;
|
||||
|
||||
use Icinga\Module\Director\Core\Json;
|
||||
use ipl\Html\Error;
|
||||
use RuntimeException;
|
||||
use function array_key_exists;
|
||||
use function is_array;
|
||||
use function is_object;
|
||||
use function is_scalar;
|
||||
|
||||
class CompareBasketObject
|
||||
{
|
||||
public static function normalize(&$value)
|
||||
{
|
||||
if (is_scalar($value)) {
|
||||
return;
|
||||
}
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $k => &$v) {
|
||||
static::normalize($v);
|
||||
}
|
||||
unset($v);
|
||||
}
|
||||
if (is_object($value)) {
|
||||
$sorted = (array) $value;
|
||||
// foreign baskets might not sort as we do:
|
||||
ksort($sorted);
|
||||
foreach ($sorted as $k => &$v) {
|
||||
static::normalize($v);
|
||||
}
|
||||
unset($v);
|
||||
$value = $sorted;
|
||||
|
||||
// foreign baskets might not sort those lists correctly:
|
||||
if (isset($value->list_name) && isset($value->entries)) {
|
||||
static::sortListBy('entry_name', $value->entries);
|
||||
}
|
||||
if (isset($value->fields)) {
|
||||
static::sortListBy('datafield_id', $value->fields);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static function sortListBy($key, &$list)
|
||||
{
|
||||
usort($list, function ($a, $b) use ($key) {
|
||||
return $a->$key > $b->$key;
|
||||
});
|
||||
}
|
||||
|
||||
public static function equals($a, $b)
|
||||
{
|
||||
if (is_scalar($a)) {
|
||||
return $a === $b;
|
||||
}
|
||||
|
||||
if ($a === null) {
|
||||
return $b === null;
|
||||
}
|
||||
|
||||
// Well... this is annoying :-/
|
||||
$a = Json::decode(Json::encode($a, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
||||
$b = Json::decode(Json::encode($b, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
||||
if (is_array($a)) {
|
||||
// Empty arrays VS empty objects :-( This is a fallback, not needed unless en/decode takes place
|
||||
if (empty($a) && is_object($b) && (array) $b === []) {
|
||||
return true;
|
||||
}
|
||||
if (! is_array($b)) {
|
||||
return false;
|
||||
}
|
||||
if (array_keys($a) !== array_keys($b)) {
|
||||
return false;
|
||||
}
|
||||
foreach ($a as $k => $v) {
|
||||
if (array_key_exists($k, $b) && static::equals($b[$k], $v)) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_object($a)) {
|
||||
// Well... empty arrays VS empty objects :-(
|
||||
if ($b === [] && (array) $a === []) {
|
||||
return true;
|
||||
}
|
||||
if (! is_object($b)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Workaround, same as above
|
||||
if (isset($a->list_name) && isset($a->entries)) {
|
||||
if (! isset($b->entries)) {
|
||||
return false;
|
||||
}
|
||||
static::sortListBy('entry_name', $a->entries);
|
||||
static::sortListBy('entry_name', $b->entries);
|
||||
}
|
||||
if (isset($a->fields) && isset($b->fields)) {
|
||||
static::sortListBy('datafield_id', $a->fields);
|
||||
static::sortListBy('datafield_id', $b->fields);
|
||||
}
|
||||
foreach ((array) $a as $k => $v) {
|
||||
if (property_exists($b, $k) && static::equals($v, $b->$k)) {
|
||||
continue;
|
||||
}
|
||||
if (! property_exists($b, $k)) {
|
||||
if ($v === null) {
|
||||
continue;
|
||||
}
|
||||
// Deal with two special defaults:
|
||||
if ($k === 'set_if_format' && $v === 'string') {
|
||||
continue;
|
||||
}
|
||||
if ($k === 'disabled' && $v === false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
foreach ((array) $b as $k => $v) {
|
||||
if (! property_exists($a, $k)) {
|
||||
if ($v === null) {
|
||||
continue;
|
||||
}
|
||||
// Once again:
|
||||
if ($k === 'set_if_format' && $v === 'string') {
|
||||
continue;
|
||||
}
|
||||
if ($k === 'disabled' && $v === false) {
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new RuntimeException("Cannot compare " . Error::getPhpTypeName($a));
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ 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\DirectorObject\Automation\CompareBasketObject;
|
||||
use Icinga\Module\Director\Exception\DuplicateKeyException;
|
||||
use Icinga\Module\Director\Forms\IcingaServiceForm;
|
||||
use Icinga\Module\Director\Hook\DataTypeHook;
|
||||
@ -159,15 +160,13 @@ class DirectorDatafield extends DbObjectWithSettings
|
||||
$list = null;
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
$compare = Json::decode(Json::encode($properties));
|
||||
if ($id && static::exists($id, $db)) {
|
||||
$existing = static::loadWithAutoIncId($id, $db);
|
||||
$existingProperties = (array) $existing->export();
|
||||
unset($existingProperties['originalId']);
|
||||
if (CompareBasketObject::equals((object) $compare, (object) $existingProperties)) {
|
||||
return $existing;
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,7 +184,8 @@ class DirectorDatafield extends DbObjectWithSettings
|
||||
foreach ($candidates as $candidate) {
|
||||
$export = $candidate->export();
|
||||
unset($export->originalId);
|
||||
if (Json::encode($export) === $encoded) {
|
||||
CompareBasketObject::normalize($export);
|
||||
if (CompareBasketObject::equals($export, $compare)) {
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user