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);
}
public function listAncestorUuIds()
{
return $this->templateTree()->listAncestorUuIdsFor($this);
}
protected function templateTree()
{
return $this->templates()->tree();

View File

@ -17,6 +17,8 @@ class TemplateTree
protected $parents;
protected $parentsUuids;
protected $children;
protected $rootNodes;
@ -29,6 +31,8 @@ class TemplateTree
protected $names;
protected $uuids;
protected $templateNameToId;
public function __construct($type, Db $connection)
@ -111,6 +115,11 @@ class TemplateTree
return array_keys($this->getAncestorsFor($object));
}
public function listAncestorUuIdsFor(IcingaObject $object)
{
return $this->getAncestorsUuidsFor($object);
}
public function listChildIdsFor(IcingaObject $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)
{
$this->requireTree();
@ -183,6 +205,36 @@ class TemplateTree
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()
{
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 $list
@ -287,6 +360,38 @@ class TemplateTree
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)
{
// can not use hasBeenLoadedFromDb() when in onStore()
@ -386,6 +491,8 @@ class TemplateTree
$rootNodes = [];
$children = [];
$names = [];
$parentsUuids = [];
$uuids = [];
foreach ($templates as $row) {
$id = (int) $row->id;
$pid = (int) $row->parent_id;
@ -403,7 +510,9 @@ class TemplateTree
}
$names[$pid] = $row->parent_name;
$uuids[$pid] = $row->parent_uuid;
$parents[$id][$pid] = $row->parent_name;
$parentsUuids[$id][$pid] = $row->parent_uuid;
if (! array_key_exists($pid, $children)) {
$children[$pid] = [];
@ -412,10 +521,12 @@ class TemplateTree
$children[$pid][$id] = $row->name;
}
$this->parents = $parents;
$this->parents = $parents;
$this->parentsUuids = $parentsUuids;
$this->children = $children;
$this->rootNodes = $rootNodes;
$this->names = $names;
$this->uuids = $uuids;
$this->templateNameToId = array_flip($names);
Benchmark::measure(sprintf('"%s" Template Tree ready', $this->type));
}
@ -458,6 +569,7 @@ class TemplateTree
'object_type' => 'o.object_type',
'parent_id' => 'p.id',
'parent_name' => 'p.object_name',
'parent_uuid' => 'p.uuid'
]
)->joinLeft(
['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\Resolver\OverrideHelper;
use InvalidArgumentException;
use PDO;
use RuntimeException;
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()
{
$request = $this->request;
@ -129,10 +169,30 @@ class IcingaObjectHandler extends RequestHandler
$params = $this->request->getUrl()->getParams();
$allowsOverrides = $params->get('allowOverrides');
$type = $this->getType();
if ($object = $this->loadOptionalObject()) {
if ($this->request->getActionName() === 'variables') {
$data = ['vars' => $data];
$object = $this->loadOptionalObject();
$customProperties = $this->getCustomProperties($object);
$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);
} elseif ($request->getMethod() === 'POST') {
$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');
}
}
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;
}
}