diff --git a/library/Director/Objects/DirectorDatafield.php b/library/Director/Objects/DirectorDatafield.php index 84db068a..7a09c7a6 100644 --- a/library/Director/Objects/DirectorDatafield.php +++ b/library/Director/Objects/DirectorDatafield.php @@ -2,28 +2,30 @@ namespace Icinga\Module\Director\Objects; +use gipfl\Json\JsonString; 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\Exception\JsonEncodeException; use Icinga\Module\Director\Forms\IcingaServiceForm; use Icinga\Module\Director\Hook\DataTypeHook; use Icinga\Module\Director\Resolver\OverriddenVarsResolver; use Icinga\Module\Director\Web\Form\DirectorObjectForm; -use InvalidArgumentException; +use Ramsey\Uuid\Uuid; +use stdClass; use Zend_Form_Element as ZfElement; class DirectorDatafield extends DbObjectWithSettings { protected $table = 'director_datafield'; - protected $keyName = 'id'; - protected $autoincKeyName = 'id'; + protected $uuidColumn = 'uuid'; protected $defaultProperties = [ 'id' => null, + 'uuid' => null, 'category_id' => null, 'varname' => null, 'caption' => null, @@ -124,8 +126,10 @@ class DirectorDatafield extends DbObjectWithSettings public function export() { $plain = (object) $this->getProperties(); - $plain->originalId = $plain->id; unset($plain->id); + if ($uuid = $this->get('uuid')) { + $plain->uuid = Uuid::fromBytes($uuid)->toString(); + } $plain->settings = (object) $this->getSettings(); if (property_exists($plain->settings, 'datalist_id')) { @@ -144,63 +148,44 @@ class DirectorDatafield extends DbObjectWithSettings } /** - * @param $plain - * @param Db $db - * @param bool $replace - * @return DirectorDatafield * @throws \Icinga\Exception\NotFoundError + * @throws JsonEncodeException */ - public static function import($plain, Db $db, $replace = false) + public static function import(stdClass $plain, Db $db): DirectorDatafield { - $properties = (array) $plain; - if (isset($properties['originalId'])) { - $id = $properties['originalId']; - unset($properties['originalId']); - } else { - $id = null; - } - - if (isset($properties['settings']->datalist)) { - // Just try to load the list, import should fail if missing - $list = DirectorDatalist::load( - $properties['settings']->datalist, - $db - ); - } else { - $list = null; - } - - $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; + $dba = $db->getDbAdapter(); + if ($uuid = $plain->uuid ?? null) { + $uuid = Uuid::fromString($uuid); + if ($candidate = DirectorDatafield::loadWithUniqueId($uuid, $db)) { + self::fixOptionalDatalistReference($plain, $db); + assert($candidate instanceof DirectorDatafield); + $candidate->setProperties((array) $plain); + return $candidate; } } - - if ($list) { - unset($properties['settings']->datalist); - $properties['settings']->datalist_id = $list->get('id'); - } - - $dba = $db->getDbAdapter(); - $query = $dba->select() - ->from('director_datafield') - ->where('varname = ?', $plain->varname); + $query = $dba->select()->from('director_datafield')->where('varname = ?', $plain->varname); $candidates = DirectorDatafield::loadAll($db, $query); foreach ($candidates as $candidate) { $export = $candidate->export(); - unset($export->originalId); CompareBasketObject::normalize($export); - if (CompareBasketObject::equals($export, $compare)) { + if (CompareBasketObject::equals($export, $plain)) { return $candidate; } } + self::fixOptionalDatalistReference($plain, $db); - return static::create($properties, $db); + return static::create((array) $plain, $db); + } + + protected static function fixOptionalDatalistReference(stdClass $plain, Db $db) + { + if (isset($plain->settings->datalist)) { + // Just try to load the list, import should fail if missing + $list = DirectorDatalist::load($plain->settings->datalist, $db); + unset($plain->settings->datalist); + $plain->settings->datalist_id = $list->get('id'); + } } protected function beforeStore() diff --git a/library/Director/Objects/DirectorDatalist.php b/library/Director/Objects/DirectorDatalist.php index da70ae41..ba0e8ace 100644 --- a/library/Director/Objects/DirectorDatalist.php +++ b/library/Director/Objects/DirectorDatalist.php @@ -11,16 +11,16 @@ use Icinga\Module\Director\Exception\DuplicateKeyException; class DirectorDatalist extends DbObject implements ExportInterface { protected $table = 'director_datalist'; - protected $keyName = 'list_name'; - protected $autoincKeyName = 'id'; + protected $uuidColumn = 'uuid'; - protected $defaultProperties = array( + protected $defaultProperties = [ 'id' => null, + 'uuid' => null, 'list_name' => null, 'owner' => null - ); + ]; /** @var DirectorDatalistEntry[] */ protected $storedEntries; diff --git a/schema/mysql-migrations/upgrade_186.sql b/schema/mysql-migrations/upgrade_186.sql new file mode 100644 index 00000000..f0c8bda5 --- /dev/null +++ b/schema/mysql-migrations/upgrade_186.sql @@ -0,0 +1,35 @@ +ALTER TABLE director_datafield ADD COLUMN uuid VARBINARY(16) DEFAULT NULL AFTER id; +SET @tmp_uuid = LOWER(CONCAT( + LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'), + LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'), '-', + LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'), '-', + '4', + LPAD(HEX(FLOOR(RAND() * 0x0fff)), 3, '0'), '-', + HEX(FLOOR(RAND() * 4 + 8)), + LPAD(HEX(FLOOR(RAND() * 0x0fff)), 3, '0'), '-', + LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'), + LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'), + LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0') +)); +UPDATE director_datafield SET uuid = UNHEX(LPAD(LPAD(HEX(id), 8, '0'), 32, REPLACE(@tmp_uuid, '-', ''))) WHERE uuid IS NULL; +ALTER TABLE director_datafield MODIFY COLUMN uuid VARBINARY(16) NOT NULL, ADD UNIQUE INDEX uuid (uuid); + +ALTER TABLE director_datalist ADD COLUMN uuid VARBINARY(16) DEFAULT NULL AFTER id; +SET @tmp_uuid = LOWER(CONCAT( + LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'), + LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'), '-', + LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'), '-', + '4', + LPAD(HEX(FLOOR(RAND() * 0x0fff)), 3, '0'), '-', + HEX(FLOOR(RAND() * 4 + 8)), + LPAD(HEX(FLOOR(RAND() * 0x0fff)), 3, '0'), '-', + LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'), + LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'), + LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0') +)); +UPDATE director_datalist SET uuid = UNHEX(LPAD(LPAD(HEX(id), 8, '0'), 32, REPLACE(@tmp_uuid, '-', ''))) WHERE uuid IS NULL; +ALTER TABLE director_datalist MODIFY COLUMN uuid VARBINARY(16) NOT NULL, ADD UNIQUE INDEX uuid (uuid); + +INSERT INTO director_schema_migration + (schema_version, migration_time) + VALUES (186, NOW()); diff --git a/schema/mysql.sql b/schema/mysql.sql index ab685b37..07566686 100644 --- a/schema/mysql.sql +++ b/schema/mysql.sql @@ -177,6 +177,7 @@ CREATE TABLE director_deployment_log ( CREATE TABLE director_datalist ( id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL, + uuid VARBINARY(16) NOT NULL, list_name VARCHAR(255) NOT NULL, owner VARCHAR(255) NOT NULL, PRIMARY KEY (id), @@ -207,6 +208,7 @@ CREATE TABLE director_datafield_category ( CREATE TABLE director_datafield ( id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + uuid VARBINARY(16) NOT NULL, category_id INT(10) UNSIGNED DEFAULT NULL, varname VARCHAR(64) NOT NULL COLLATE utf8_bin, caption VARCHAR(255) NOT NULL, @@ -2443,4 +2445,4 @@ CREATE TABLE branched_icinga_dependency ( INSERT INTO director_schema_migration (schema_version, migration_time) - VALUES (184, NOW()); + VALUES (186, NOW()); diff --git a/schema/pgsql-migrations/upgrade_186.sql b/schema/pgsql-migrations/upgrade_186.sql new file mode 100644 index 00000000..cb491f6c --- /dev/null +++ b/schema/pgsql-migrations/upgrade_186.sql @@ -0,0 +1,11 @@ +ALTER TABLE director_datafield ADD COLUMN uuid bytea UNIQUE CHECK(LENGTH(uuid) = 16); +UPDATE director_datafield SET uuid = decode(replace(gen_random_uuid()::text, '-', ''), 'hex') WHERE uuid IS NULL; +ALTER TABLE director_datafield ALTER COLUMN uuid SET NOT NULL; + +ALTER TABLE director_datalist ADD COLUMN uuid bytea UNIQUE CHECK(LENGTH(uuid) = 16); +UPDATE director_datalist SET uuid = decode(replace(gen_random_uuid()::text, '-', ''), 'hex') WHERE uuid IS NULL; +ALTER TABLE director_datalist ALTER COLUMN uuid SET NOT NULL; + +INSERT INTO director_schema_migration +(schema_version, migration_time) +VALUES (186, NOW()); diff --git a/schema/pgsql.sql b/schema/pgsql.sql index 5d0e3abc..bebdbae1 100644 --- a/schema/pgsql.sql +++ b/schema/pgsql.sql @@ -246,6 +246,7 @@ CREATE INDEX start_time_idx ON director_deployment_log (start_time); CREATE TABLE director_datalist ( id serial, + uuid bytea CHECK(LENGTH(uuid) = 16) NOT NULL, list_name character varying(255) NOT NULL, owner character varying(255) NOT NULL, PRIMARY KEY (id) @@ -283,6 +284,7 @@ CREATE UNIQUE INDEX datafield_category_name ON director_datafield_category (cate CREATE TABLE director_datafield ( id serial, + uuid bytea CHECK(LENGTH(uuid) = 16) NOT NULL, category_id integer DEFAULT NULL, varname character varying(64) NOT NULL, caption character varying(255) NOT NULL, @@ -2782,4 +2784,4 @@ CREATE INDEX branched_dependency_search_object_name ON branched_icinga_dependenc INSERT INTO director_schema_migration (schema_version, migration_time) - VALUES (184, NOW()); + VALUES (186, NOW());