Check for existence of custom variable in the object before storing it

This commit is contained in:
raviks789 2025-07-10 11:55:35 +02:00
parent 6a7194475f
commit d3d4bfd0bd
No known key found for this signature in database
3 changed files with 198 additions and 4 deletions

View File

@ -3058,6 +3058,11 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
return $this->templateTree()->listAncestorIdsFor($this); return $this->templateTree()->listAncestorIdsFor($this);
} }
public function listAncestorUuIds()
{
return $this->templateTree()->listAncestorUuIdsFor($this);
}
protected function templateTree() protected function templateTree()
{ {
return $this->templates()->tree(); return $this->templates()->tree();

View File

@ -17,6 +17,8 @@ class TemplateTree
protected $parents; protected $parents;
protected $parentsUuids;
protected $children; protected $children;
protected $rootNodes; protected $rootNodes;
@ -29,6 +31,8 @@ class TemplateTree
protected $names; protected $names;
protected $uuids;
protected $templateNameToId; protected $templateNameToId;
public function __construct($type, Db $connection) public function __construct($type, Db $connection)
@ -111,6 +115,11 @@ class TemplateTree
return array_keys($this->getAncestorsFor($object)); return array_keys($this->getAncestorsFor($object));
} }
public function listAncestorUuIdsFor(IcingaObject $object)
{
return $this->getAncestorsUuidsFor($object);
}
public function listChildIdsFor(IcingaObject $object) public function listChildIdsFor(IcingaObject $object)
{ {
return array_keys($this->getChildrenFor($object)); return array_keys($this->getChildrenFor($object));
@ -148,6 +157,19 @@ class TemplateTree
} }
} }
public function getAncestorsUuidsFor(IcingaObject $object)
{
if (
$object->hasBeenModified()
&& $object->gotImports()
&& $object->imports()->hasBeenModified()
) {
return $this->getAncestorsUuidsForUnstoredObject($object);
} else {
return $this->getParentsUuidsById($object->getProperty('id'));
}
}
protected function getAncestorsForUnstoredObject(IcingaObject $object) protected function getAncestorsForUnstoredObject(IcingaObject $object)
{ {
$this->requireTree(); $this->requireTree();
@ -183,6 +205,36 @@ class TemplateTree
return $ancestors; return $ancestors;
} }
protected function getAncestorsUuidsForUnstoredObject(IcingaObject $object)
{
$this->requireTree();
$ancestors = [];
foreach ($object->imports() as $import) {
$name = $import->get('uuid');
if ($import->hasBeenLoadedFromDb()) {
$pid = (int) $import->get('id');
} else {
if (! array_key_exists($name, $this->templateNameToId)) {
continue;
}
$pid = $this->templateNameToId[$name];
}
$this->getAncestorsUuidsById($pid, $ancestors);
$uuid = $import->get('uuid');
// Hint: inheritance order matters
if (false !== ($key = array_search($uuid, $ancestors))) {
unset($ancestors[$key]);
}
$ancestors[$pid] = $uuid;
}
return $ancestors;
}
protected function requireObjectMaps() protected function requireObjectMaps()
{ {
if ($this->objectMaps === null) { if ($this->objectMaps === null) {
@ -211,6 +263,27 @@ class TemplateTree
} }
} }
public function getParentsUuidsById($id)
{
$this->requireTree();
if (array_key_exists($id, $this->parentsUuids)) {
return $this->parentsUuids[$id];
}
$this->requireObjectMaps();
if (array_key_exists($id, $this->objectMaps)) {
$parentsUuids = [];
foreach ($this->objectMaps[$id] as $pid) {
$parentsUuids[$pid] = $this->uuids[$pid];
}
return $parentsUuids;
} else {
return [];
}
}
/** /**
* @param $id * @param $id
* @param $list * @param $list
@ -287,6 +360,38 @@ class TemplateTree
return $ancestors; return $ancestors;
} }
/**
* Get the ancestorUuids for the given object ID
*
* @param $id
* @param array $ancestors
* @param array $path
*
* @return array
*/
public function getAncestorsUuidsById($id, &$ancestors = [], $path = [])
{
$path[$id] = true;
foreach ($this->getParentsUuidsById($id) as $pid => $uuid) {
$this->assertNotInList($pid, $path);
$path[$pid] = true;
$this->getAncestorsUuidsById($pid, $ancestors, $path);
unset($path[$pid]);
// Hint: inheritance order matters
if (false !== ($key = array_search($uuid, $ancestors))) {
unset($ancestors[$key]);
}
$ancestors[$pid] = $uuid;
}
unset($path[$id]);
return $ancestors;
}
public function getChildrenFor(IcingaObject $object) public function getChildrenFor(IcingaObject $object)
{ {
// can not use hasBeenLoadedFromDb() when in onStore() // can not use hasBeenLoadedFromDb() when in onStore()
@ -386,6 +491,8 @@ class TemplateTree
$rootNodes = []; $rootNodes = [];
$children = []; $children = [];
$names = []; $names = [];
$parentsUuids = [];
$uuids = [];
foreach ($templates as $row) { foreach ($templates as $row) {
$id = (int) $row->id; $id = (int) $row->id;
$pid = (int) $row->parent_id; $pid = (int) $row->parent_id;
@ -403,7 +510,9 @@ class TemplateTree
} }
$names[$pid] = $row->parent_name; $names[$pid] = $row->parent_name;
$uuids[$pid] = $row->parent_uuid;
$parents[$id][$pid] = $row->parent_name; $parents[$id][$pid] = $row->parent_name;
$parentsUuids[$id][$pid] = $row->parent_uuid;
if (! array_key_exists($pid, $children)) { if (! array_key_exists($pid, $children)) {
$children[$pid] = []; $children[$pid] = [];
@ -413,9 +522,11 @@ class TemplateTree
} }
$this->parents = $parents; $this->parents = $parents;
$this->parentsUuids = $parentsUuids;
$this->children = $children; $this->children = $children;
$this->rootNodes = $rootNodes; $this->rootNodes = $rootNodes;
$this->names = $names; $this->names = $names;
$this->uuids = $uuids;
$this->templateNameToId = array_flip($names); $this->templateNameToId = array_flip($names);
Benchmark::measure(sprintf('"%s" Template Tree ready', $this->type)); Benchmark::measure(sprintf('"%s" Template Tree ready', $this->type));
} }
@ -458,6 +569,7 @@ class TemplateTree
'object_type' => 'o.object_type', 'object_type' => 'o.object_type',
'parent_id' => 'p.id', 'parent_id' => 'p.id',
'parent_name' => 'p.object_name', 'parent_name' => 'p.object_name',
'parent_uuid' => 'p.uuid'
] ]
)->joinLeft( )->joinLeft(
['i' => $table . '_inheritance'], ['i' => $table . '_inheritance'],

View File

@ -14,6 +14,7 @@ use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaObject; use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Resolver\OverrideHelper; use Icinga\Module\Director\Resolver\OverrideHelper;
use InvalidArgumentException; use InvalidArgumentException;
use PDO;
use RuntimeException; use RuntimeException;
class IcingaObjectHandler extends RequestHandler class IcingaObjectHandler extends RequestHandler
@ -96,6 +97,45 @@ class IcingaObjectHandler extends RequestHandler
} }
} }
public function getCustomProperties(IcingaObject $object): array
{
if ($object->get('uuid') === null) {
return [];
}
$type = $object->getShortTableName();
$db = $object->getConnection();
$uuids = $object->listAncestorUuIds();
$query = $db->getDbAdapter()
->select()
->from(
['dp' => 'director_property'],
[
'key_name' => 'dp.key_name',
'uuid' => 'dp.uuid',
'value_type' => 'dp.value_type',
'label' => 'dp.label',
'instantiable' => 'dp.instantiable',
'required' => 'iop.required',
'children' => 'COUNT(cdp.uuid)'
]
)
->join(['iop' => "icinga_$type" . '_property'], 'dp.uuid = iop.property_uuid', [])
->joinLeft(['cdp' => 'director_property'], 'cdp.parent_uuid = dp.uuid', [])
->where('iop.' . $type . '_uuid IN (?)', $uuids)
->group(['dp.uuid', 'dp.key_name', 'dp.value_type', 'dp.label', 'dp.instantiable', 'iop.required'])
->order('children')
->order('instantiable')
->order('key_name');
$result = [];
foreach ($db->getDbAdapter()->fetchAll($query, fetchMode: PDO::FETCH_ASSOC) as $row) {
$result[$row['key_name']] = $row;
}
return $result;
}
protected function handleApiRequest() protected function handleApiRequest()
{ {
$request = $this->request; $request = $this->request;
@ -129,10 +169,30 @@ class IcingaObjectHandler extends RequestHandler
$params = $this->request->getUrl()->getParams(); $params = $this->request->getUrl()->getParams();
$allowsOverrides = $params->get('allowOverrides'); $allowsOverrides = $params->get('allowOverrides');
$type = $this->getType(); $type = $this->getType();
if ($object = $this->loadOptionalObject()) { $object = $this->loadOptionalObject();
if ($this->request->getActionName() === 'variables') { $customProperties = $this->getCustomProperties($object);
$data = ['vars' => $data]; $overridenCustomVars = $this->getCustomVarsFromData($data);
if (! empty($overridenCustomVars)) {
$diff = array_diff(array_keys($data), array_keys($customProperties));
if (! empty($diff)) {
throw new Exception(sprintf(
"The custom properties (%s) are not supported by this object",
implode(", ", $diff)
));
}
}
if ($object) {
if ($this->request->getActionName() === 'variables') {
$diff = array_diff(array_keys($data), array_keys($customProperties));
if (! empty($diff)) {
throw new Exception(sprintf(
"The custom properties %s are not supported by this object",
implode(", ", $diff)
));
}
$data = ['vars' => $data];
$object->setProperties($data); $object->setProperties($data);
} elseif ($request->getMethod() === 'POST') { } elseif ($request->getMethod() === 'POST') {
$object->setProperties($data); $object->setProperties($data);
@ -197,4 +257,21 @@ class IcingaObjectHandler extends RequestHandler
throw new RuntimeException('Found a single service, which should have been found (and dealt with) before'); throw new RuntimeException('Found a single service, which should have been found (and dealt with) before');
} }
} }
private function getCustomVarsFromData(array $data): array
{
$customVars = [];
foreach ($data as $key => $value) {
if ($key === 'vars') {
$customVars = $value;
}
if (substr($key, 0, 5) === 'vars.') {
$customVars[substr($key, 5)] = $value;
}
}
return $customVars;
}
} }