summaryrefslogtreecommitdiffstats
path: root/library/Director/Data
diff options
context:
space:
mode:
Diffstat (limited to 'library/Director/Data')
-rw-r--r--library/Director/Data/AssignFilterHelper.php2
-rw-r--r--library/Director/Data/Db/DbDataFormatter.php16
-rw-r--r--library/Director/Data/Db/DbObject.php73
-rw-r--r--library/Director/Data/Db/ServiceSetQueryBuilder.php2
-rw-r--r--library/Director/Data/Exporter.php45
-rw-r--r--library/Director/Data/FieldReferenceLoader.php4
-rw-r--r--library/Director/Data/HostServiceLoader.php12
-rw-r--r--library/Director/Data/ObjectImporter.php168
-rw-r--r--library/Director/Data/PropertyMangler.php4
9 files changed, 295 insertions, 31 deletions
diff --git a/library/Director/Data/AssignFilterHelper.php b/library/Director/Data/AssignFilterHelper.php
index b0253cf..7448c51 100644
--- a/library/Director/Data/AssignFilterHelper.php
+++ b/library/Director/Data/AssignFilterHelper.php
@@ -135,7 +135,7 @@ class AssignFilterHelper
$parts = array();
foreach (preg_split('~\*~', $expression) as $part) {
- $parts[] = preg_quote($part);
+ $parts[] = preg_quote($part, '/');
}
// match() is case insensitive
$pattern = '/^' . implode('.*', $parts) . '$/i';
diff --git a/library/Director/Data/Db/DbDataFormatter.php b/library/Director/Data/Db/DbDataFormatter.php
index d6e4eeb..91fc776 100644
--- a/library/Director/Data/Db/DbDataFormatter.php
+++ b/library/Director/Data/Db/DbDataFormatter.php
@@ -6,7 +6,7 @@ use InvalidArgumentException;
class DbDataFormatter
{
- public static function normalizeBoolean($value)
+ public static function normalizeBoolean($value): ?string
{
if ($value === 'y' || $value === '1' || $value === true || $value === 1) {
return 'y';
@@ -20,7 +20,19 @@ class DbDataFormatter
throw new InvalidArgumentException(sprintf(
'Got invalid boolean: %s',
- var_export($value, 1)
+ var_export($value, true)
));
}
+
+ public static function booleanForDbValue($value): ?bool
+ {
+ if ($value === 'y') {
+ return true;
+ }
+ if ($value === 'n') {
+ return false;
+ }
+
+ return $value; // let this fail elsewhere, if not null
+ }
}
diff --git a/library/Director/Data/Db/DbObject.php b/library/Director/Data/Db/DbObject.php
index 6ecae8b..114b61b 100644
--- a/library/Director/Data/Db/DbObject.php
+++ b/library/Director/Data/Db/DbObject.php
@@ -80,6 +80,9 @@ abstract class DbObject
protected $binaryProperties = [];
+ /* key/value!! */
+ protected $booleans = [];
+
/**
* Filled with object instances when prefetchAll is used
*/
@@ -346,6 +349,16 @@ abstract class DbObject
return $this->$func($value);
}
+ if ($this->getUuidColumn() === $key) {
+ if (strlen($value) > 16) {
+ $value = Uuid::fromString($value)->getBytes();
+ }
+ }
+
+ if ($this->propertyIsBoolean($key)) {
+ $value = DbDataFormatter::normalizeBoolean($value);
+ }
+
if (! $this->hasProperty($key)) {
throw new InvalidArgumentException(sprintf(
'Trying to set invalid key "%s"',
@@ -372,7 +385,10 @@ abstract class DbObject
return $this;
}
if ($key === 'id' || substr($key, -3) === '_id') {
- if ((int) $value === (int) $this->properties[$key]) {
+ if ($value !== null
+ && $this->properties[$key] !== null
+ && (int) $value === (int) $this->properties[$key]
+ ) {
return $this;
}
}
@@ -553,7 +569,7 @@ abstract class DbObject
/**
* Unique key name
*
- * @return string
+ * @return string|array
*/
public function getKeyName()
{
@@ -706,8 +722,7 @@ abstract class DbObject
*/
protected function loadFromDb()
{
- $select = $this->db->select()->from($this->table)->where($this->createWhere());
- $properties = $this->db->fetchRow($select);
+ $properties = $this->db->fetchRow($this->prepareObjectQuery());
if (empty($properties)) {
if (is_array($this->getKeyName())) {
@@ -728,6 +743,11 @@ abstract class DbObject
return $this->setDbProperties($properties);
}
+ public function prepareObjectQuery()
+ {
+ return $this->db->select()->from($this->table)->where($this->createWhere());
+ }
+
/**
* @param object|array $row
* @param Db $db
@@ -878,6 +898,11 @@ abstract class DbObject
return in_array($column, $this->binaryProperties) || $this->getUuidColumn() === $column;
}
+ public function propertyIsBoolean($property)
+ {
+ return array_key_exists($property, $this->booleans);
+ }
+
/**
* Store object to database
*
@@ -959,7 +984,7 @@ abstract class DbObject
$this->table,
$this->getLogId(),
$e->getMessage(),
- var_export($this->getProperties(), 1) // TODO: Remove properties
+ var_export($this->getProperties(), true) // TODO: Remove properties
));
}
@@ -1027,7 +1052,7 @@ abstract class DbObject
if ($this->hasUuidColumn() && $this->properties[$this->uuidColumn] !== null) {
return $this->db->quoteInto(
sprintf('%s = ?', $this->getUuidColumn()),
- $this->connection->quoteBinary($this->getUniqueId()->getBytes())
+ $this->connection->quoteBinary($this->getOriginalProperty($this->uuidColumn))
);
}
if ($id = $this->getAutoincId()) {
@@ -1302,6 +1327,40 @@ abstract class DbObject
}
/**
+ * @param $id
+ * @param DbConnection $connection
+ * @return static
+ */
+ public static function loadOptional($id, DbConnection $connection): ?DbObject
+ {
+ if ($prefetched = static::getPrefetched($id)) {
+ return $prefetched;
+ }
+ /** @var DbObject $obj */
+ $obj = new static();
+
+ if (self::$dbObjectStore !== null && $obj->hasUuidColumn()) {
+ $table = $obj->getTableName();
+ assert($connection instanceof Db);
+ $uuid = UuidLookup::findUuidForKey($id, $table, $connection, self::$dbObjectStore->getBranch());
+ if ($uuid) {
+ return self::$dbObjectStore->load($table, $uuid);
+ }
+
+ return null;
+ }
+
+ $obj->setConnection($connection)->setKey($id);
+ $properties = $connection->getDbAdapter()->fetchRow($obj->prepareObjectQuery());
+ if (empty($properties)) {
+ return null;
+ }
+
+ $obj->setDbProperties($properties);
+ return $obj;
+ }
+
+ /**
* @param DbConnection $connection
* @param \Zend_Db_Select $query
* @param string|null $keyColumn
@@ -1436,7 +1495,7 @@ abstract class DbObject
));
}
- public static function loadWithUniqueId(UuidInterface $uuid, DbConnection $connection)
+ public static function loadWithUniqueId(UuidInterface $uuid, DbConnection $connection): ?DbObject
{
$db = $connection->getDbAdapter();
$obj = new static;
diff --git a/library/Director/Data/Db/ServiceSetQueryBuilder.php b/library/Director/Data/Db/ServiceSetQueryBuilder.php
index 7841d1e..597fe0e 100644
--- a/library/Director/Data/Db/ServiceSetQueryBuilder.php
+++ b/library/Director/Data/Db/ServiceSetQueryBuilder.php
@@ -27,6 +27,8 @@ class ServiceSetQueryBuilder
/** @var \Zend_Db_Adapter_Abstract */
protected $db;
+ protected $searchColumns = [];
+
/**
* @param ?UuidInterface $uuid
*/
diff --git a/library/Director/Data/Exporter.php b/library/Director/Data/Exporter.php
index a2e3191..1a3cfcb 100644
--- a/library/Director/Data/Exporter.php
+++ b/library/Director/Data/Exporter.php
@@ -2,10 +2,14 @@
namespace Icinga\Module\Director\Data;
+use gipfl\Json\JsonString;
use gipfl\ZfDb\Adapter\Adapter;
+use Icinga\Authentication\Auth;
+use Icinga\Module\Director\Data\Db\DbDataFormatter;
use Icinga\Module\Director\Data\Db\DbObject;
use Icinga\Module\Director\Data\Db\DbObjectWithSettings;
use Icinga\Module\Director\Db;
+use Icinga\Module\Director\DirectorObject\Automation\Basket;
use Icinga\Module\Director\Objects\DirectorDatafield;
use Icinga\Module\Director\Objects\DirectorDatalist;
use Icinga\Module\Director\Objects\DirectorDatalistEntry;
@@ -18,6 +22,7 @@ use Icinga\Module\Director\Objects\IcingaTemplateChoice;
use Icinga\Module\Director\Objects\ImportSource;
use Icinga\Module\Director\Objects\InstantiatedViaHook;
use Icinga\Module\Director\Objects\SyncRule;
+use Ramsey\Uuid\Uuid;
use RuntimeException;
class Exporter
@@ -68,6 +73,11 @@ class Exporter
$props = $chosen;
}
+ if ($column = $object->getUuidColumn()) {
+ if ($uuid = $object->get($column)) {
+ $props[$column] = Uuid::fromBytes($uuid)->toString();
+ }
+ }
ksort($props);
return (object) $props;
@@ -152,10 +162,10 @@ class Exporter
throw new RuntimeException('Not yet');
}
$props['services'] = [];
- foreach ($object->getServiceObjects() as $serviceObject) {
- $props['services'][$serviceObject->getObjectName()] = $this->export($serviceObject);
+ foreach ($object->getServices() as $serviceObject) {
+ $props['services'][] = $this->export($serviceObject);
}
- ksort($props['services']);
+ usort($props['services'], [$this, 'sortByName']);
} elseif ($object instanceof IcingaHost) {
if ($this->exportHostServices) {
$services = [];
@@ -168,10 +178,15 @@ class Exporter
}
}
+ protected function sortByName($left, $right)
+ {
+ return $left->object_name < $right->object_name ? '-1' : '1';
+ }
+
public function serviceLoader()
{
if ($this->serviceLoader === null) {
- $this->serviceLoader = new HostServiceLoader($this->connection);
+ $this->serviceLoader = new HostServiceLoader($this->connection, Auth::getInstance());
$this->serviceLoader->resolveObjects($this->resolveObjects);
}
@@ -241,6 +256,12 @@ class Exporter
protected function exportDbObject(DbObject $object)
{
$props = $object->getProperties();
+ foreach ($props as $key => &$value) {
+ if ($object->propertyIsBoolean($key)) {
+ $value = DbDataFormatter::booleanForDbValue($value);
+ }
+ }
+ unset($value);
if ($object instanceof DbObjectWithSettings) {
if ($object instanceof InstantiatedViaHook) {
$props['settings'] = (object) $object->getInstance()->exportSettings();
@@ -248,6 +269,11 @@ class Exporter
$props['settings'] = (object) $object->getSettings(); // Already sorted
}
}
+ if ($object instanceof Basket) {
+ if (isset($props['objects']) && is_string($props['objects'])) {
+ $props['objects'] = JsonString::decode($props['objects']);
+ }
+ }
unset($props['uuid']); // Not yet
if (! $this->showDefaults) {
foreach ($props as $key => $value) {
@@ -279,16 +305,7 @@ class Exporter
protected function exportDatalistEntries(DirectorDatalist $list)
{
$entries = [];
- $id = $list->get('id');
- if ($id === null) {
- return $entries;
- }
-
- $dbEntries = DirectorDatalistEntry::loadAllForList($list);
- // Hint: they are loaded with entry_name key
- ksort($dbEntries);
-
- foreach ($dbEntries as $entry) {
+ foreach ($list->getEntries() as $name => $entry) {
if ($entry->shouldBeRemoved()) {
continue;
}
diff --git a/library/Director/Data/FieldReferenceLoader.php b/library/Director/Data/FieldReferenceLoader.php
index 1e3d92e..99a9925 100644
--- a/library/Director/Data/FieldReferenceLoader.php
+++ b/library/Director/Data/FieldReferenceLoader.php
@@ -29,12 +29,12 @@ class FieldReferenceLoader
}
$type = $object->getShortTableName();
$res = $db->fetchAll(
- $db->select()->from(['f' => "icinga_${type}_field"], [
+ $db->select()->from(['f' => "icinga_{$type}_field"], [
'f.datafield_id',
'f.is_required',
'f.var_filter',
])->join(['df' => 'director_datafield'], 'df.id = f.datafield_id', [])
- ->where("${type}_id = ?", (int) $id)
+ ->where("{$type}_id = ?", (int) $id)
->order('varname ASC')
);
diff --git a/library/Director/Data/HostServiceLoader.php b/library/Director/Data/HostServiceLoader.php
index 4cc4b96..c8bd8b9 100644
--- a/library/Director/Data/HostServiceLoader.php
+++ b/library/Director/Data/HostServiceLoader.php
@@ -4,6 +4,7 @@ namespace Icinga\Module\Director\Data;
use gipfl\IcingaWeb2\Table\QueryBasedTable;
use gipfl\ZfDb\Select;
+use Icinga\Authentication\Auth;
use Icinga\Data\SimpleQuery;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Db\AppliedServiceSetLoader;
@@ -26,21 +27,26 @@ class HostServiceLoader
/** @var \Zend_Db_Adapter_Abstract */
protected $db;
+ /** @var Auth */
+ protected $auth;
+
/** @var bool */
protected $resolveHostServices = false;
/** @var bool */
protected $resolveObjects = false;
- public function __construct(Db $connection)
+ public function __construct(Db $connection, Auth $auth)
{
$this->connection = $connection;
$this->db = $connection->getDbAdapter();
+ $this->auth = $auth;
}
public function fetchServicesForHost(IcingaHost $host)
{
- $table = (new ObjectsTableService($this->connection))->setHost($host);
+ $table = (new ObjectsTableService($this->connection, $this->auth))
+ ->setHost($host);
$services = $this->fetchServicesForTable($table);
if ($this->resolveHostServices) {
foreach ($this->fetchAllServicesForHost($host) as $service) {
@@ -69,7 +75,7 @@ class HostServiceLoader
/** @var IcingaHost[] $parents */
$parents = IcingaTemplateRepository::instanceByObject($host)->getTemplatesFor($host, true);
foreach ($parents as $parent) {
- $table = (new ObjectsTableService($this->connection))
+ $table = (new ObjectsTableService($this->connection, $this->auth))
->setHost($parent)
->setInheritedBy($host);
foreach ($this->fetchServicesForTable($table) as $service) {
diff --git a/library/Director/Data/ObjectImporter.php b/library/Director/Data/ObjectImporter.php
new file mode 100644
index 0000000..231ad1c
--- /dev/null
+++ b/library/Director/Data/ObjectImporter.php
@@ -0,0 +1,168 @@
+<?php
+
+namespace Icinga\Module\Director\Data;
+
+use gipfl\Json\JsonDecodeException;
+use gipfl\Json\JsonString;
+use Icinga\Module\Director\Data\Db\DbObject;
+use Icinga\Module\Director\Db;
+use Icinga\Module\Director\DirectorObject\Automation\Basket;
+use Icinga\Module\Director\Objects\DirectorJob;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaService;
+use Icinga\Module\Director\Objects\IcingaServiceSet;
+use Icinga\Module\Director\Objects\ImportSource;
+use Icinga\Module\Director\Objects\SyncRule;
+use InvalidArgumentException;
+use Ramsey\Uuid\Uuid;
+use stdClass;
+
+class ObjectImporter
+{
+ protected static $templatesOnly = [
+ IcingaHost::class,
+ IcingaService::class,
+ IcingaServiceSet::class,
+ ];
+
+ /** @var Db */
+ protected $db;
+
+ public function __construct(Db $db)
+ {
+ $this->db = $db;
+ }
+
+ /**
+ * @param class-string|DbObject $implementation
+ * @param stdClass $plain
+ * @return DbObject
+ * @throws JsonDecodeException
+ */
+ public function import(string $implementation, stdClass $plain): DbObject
+ {
+ $this->assertTemplate($implementation, $plain);
+ $this->fixRelations($implementation, $plain);
+ $this->applyOtherWorkarounds($implementation, $plain);
+ $this->fixLegacyBaskets($implementation, $plain);
+ $this->fixSubObjects($implementation, $plain);
+
+ $object = $this->loadExistingObject($implementation, $plain);
+ if ($object === null) {
+ $object = $implementation::create([], $this->db);
+ }
+
+ $properties = (array) $plain;
+ unset($properties['fields']);
+ unset($properties['originalId']);
+ if ($implementation === Basket::class) {
+ if (isset($properties['objects']) && is_string($properties['objects'])) {
+ $properties['objects'] = JsonString::decode($properties['objects']);
+ }
+ }
+ $object->setProperties($properties);
+
+ return $object;
+ }
+
+ protected function fixLegacyBaskets(string $implementation, stdClass $plain)
+ {
+ // TODO: Check, whether current export sets modifiers = [] in case there is none
+ if ($implementation == ImportSource::class) {
+ if (!isset($plain->modifiers)) {
+ $plain->modifiers = [];
+ }
+ }
+ }
+
+ protected function applyOtherWorkarounds(string $implementation, stdClass $plain)
+ {
+ if ($implementation === SyncRule::class) {
+ if (isset($plain->properties)) {
+ $plain->syncProperties = $plain->properties;
+ unset($plain->properties);
+ }
+ }
+ }
+
+ protected function fixSubObjects(string $implementation, stdClass $plain)
+ {
+ if ($implementation === IcingaServiceSet::class) {
+ foreach ($plain->services as $service) {
+ unset($service->fields);
+ }
+ // Hint: legacy baskets are carrying service names as object keys, new baskets have arrays
+ $plain->services = array_values((array) $plain->services);
+ }
+ }
+
+ protected function fixRelations(string $implementation, stdClass $plain)
+ {
+ if ($implementation === DirectorJob::class) {
+ $settings = $plain->settings;
+ $source = $settings->source ?? null;
+ if ($source && !isset($settings->source_id)) {
+ $settings->source_id = ImportSource::load($source, $this->db)->get('id');
+ unset($settings->source);
+ }
+ $rule = $settings->rule ?? null;
+ if ($rule && !isset($settings->rule_id)) {
+ $settings->rule_id = SyncRule::load($rule, $this->db)->get('id');
+ unset($settings->rule);
+ }
+ }
+ }
+
+ /**
+ * @param class-string<DbObject> $implementation
+ * @param stdClass $plain
+ * @return DbObject|null
+ */
+ protected function loadExistingObject(string $implementation, stdClass $plain): ?DbObject
+ {
+ if (isset($plain->uuid)
+ && $instance = $implementation::loadWithUniqueId(Uuid::fromString($plain->uuid), $this->db)
+ ) {
+ return $instance;
+ }
+
+ if ($implementation === IcingaService::class) {
+ $key = [
+ 'object_type' => 'template',
+ 'object_name' => $plain->object_name
+ ];
+ } else {
+ $dummy = $implementation::create();
+ $keyColumn = $dummy->getKeyName();
+ if (is_array($keyColumn)) {
+ if (empty($keyColumn)) {
+ throw new \RuntimeException("$implementation has an empty keyColumn array");
+ }
+ $key = [];
+ foreach ($keyColumn as $column) {
+ if (isset($plain->$column)) {
+ $key[$column] = $plain->$column;
+ }
+ }
+ } else {
+ $key = $plain->$keyColumn;
+ }
+ }
+
+ return $implementation::loadOptional($key, $this->db);
+ }
+
+ protected function assertTemplate(string $implementation, stdClass $plain)
+ {
+ if (! in_array($implementation, self::$templatesOnly)) {
+ return;
+ }
+ if ($plain->object_type !== 'template') {
+ throw new InvalidArgumentException(sprintf(
+ 'Can import only Templates, got "%s" for "%s"',
+ $plain->object_type,
+ $plain->name
+ ));
+ }
+ }
+}
diff --git a/library/Director/Data/PropertyMangler.php b/library/Director/Data/PropertyMangler.php
index a457f1d..40b2570 100644
--- a/library/Director/Data/PropertyMangler.php
+++ b/library/Director/Data/PropertyMangler.php
@@ -19,7 +19,7 @@ class PropertyMangler
throw new InvalidArgumentException(sprintf(
'I can only append to arrays, %s is %s',
$key,
- var_export($current, 1)
+ var_export($current, true)
));
}
@@ -52,7 +52,7 @@ class PropertyMangler
throw new InvalidArgumentException(sprintf(
'I can only remove strings or from arrays, %s is %s',
$key,
- var_export($current, 1)
+ var_export($current, true)
));
}
}