Skip to content

Commit

Permalink
Servers transmogrify
Browse files Browse the repository at this point in the history
  • Loading branch information
Ioannis committed Oct 18, 2025
1 parent c47ae95 commit 0b70f08
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 22 deletions.
94 changes: 80 additions & 14 deletions app/plugins/Transmogrify/config/schema/tables.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
},
"__NOTES__": "Common keys: source (DB table), displayField (for printing), addChangelog (add created/modified history), booleans (type coercion), cache (key(s) to cache rows by), sqlSelect (custom SELECT provider function), pre/postTable and pre/postRow (hook function names), fieldMap (left=new table column, right=source column name or &function). Use &mapNow to inject current timestamp, &mapExtendedType to resolve cm_co_extended_types, and mapping helpers found in Transmogrify traits. Note: There is no generic config-driven enforcement for required/defaults/unique; enforce uniqueness via database constraints."
},
"__NOTES__": "CONFIGURATION MIGRATIONS",
"cos": {
"source": "cm_cos",
"displayField": "name",
Expand Down Expand Up @@ -84,10 +85,6 @@
"t_and_c_return_url_allowlist": null
}
},
"authentication_events": {
"source": "cm_authentication_events",
"displayField": "authenticated_identifier"
},
"api_users": {
"source": "cm_api_users",
"displayField": "username",
Expand All @@ -101,6 +98,85 @@
"displayField": "name",
"sqlSelect": "couSqlSelect"
},
"servers": {
"source": "cm_servers",
"displayField": "description",
"addChangelog": false,
"cache": ["co_id", "status"],
"fieldMap": {
"plugin": "&mapServerTypeToPlugin",
"server_type": null
}
},
"http_servers": {
"source": "cm_http_servers",
"displayField": "serverurl",
"addChangelog": false,
"booleans": [
"ssl_verify_host",
"ssl_verify_peer"
],
"cache": ["server_id"],
"fieldMap": {
"serverurl": "url",
"ssl_verify_peer": "skip_ssl_verification",
"ssl_verify_host": null
}
},
"oauth2_servers": {
"source": "cm_oauth2_servers",
"displayField": "serverurl",
"addChangelog": false,
"cache": ["server_id"],
"booleans": ["access_token_exp"],
"fieldMap": {
"serverurl": "url",
"proxy": null
}
},
"sql_servers": {
"source": "cm_sql_servers",
"displayField": "hostname",
"cache": ["server_id"],
"addChangelog": false,
"fieldMap": {
"dbport": "port"
}
},
"match_servers": {
"source": "cm_match_servers",
"displayField": "username",
"addChangelog": false,
"booleans": [
"ssl_verify_peer",
"ssl_verify_host"
],
"cache": ["server_id"],
"fieldMap": {
"serverurl": "url",
"auth_type": "?BA",
"ssl_verify_peer": "skip_ssl_verification",
"ssl_verify_host": null
}
},
"match_server_attributes": {
"source": "cm_match_server_attributes",
"displayField": "attribute",
"addChangelog": false,
"booleans": [
"required"
],
"cache": ["match_server_id"],
"fieldMap": {
"type_id": "&mapMatchAttributeTypeId",
"type": null
}
},
"__NOTES__": "DATA MIGRATIONS",
"authentication_events": {
"source": "cm_authentication_events",
"displayField": "authenticated_identifier"
},
"people": {
"source": "cm_co_people",
"displayField": "id",
Expand Down Expand Up @@ -320,15 +396,5 @@
"co_person_id": "person_id",
"org_identity_id": "external_identity_id"
}
},
"servers": {
"source": "cm_servers",
"displayField": "description",
"addChangelog": false,
"cache": ["co_id", "status"],
"fieldMap": {
"plugin": "&mapServerTypeToPlugin",
"server_type": null
}
}
}
16 changes: 8 additions & 8 deletions app/plugins/Transmogrify/src/Command/TransmogrifyCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,8 @@ public function execute(Arguments $args, ConsoleIo $io): int

foreach(array_keys($this->tables) as $t) {
// Initializations per table migration
$modeltableEmpty = true;
$notSelected = false;
$outboundTableEmpty = true;
$skipTableTransmogrification = false;
$inboundQualifiedTableName = $this->inconn->qualifyTableName($this->tables[$t]['source']);
$outboundQualifiedTableName = $this->outconn->qualifyTableName($t);
$Model = TableRegistry::getTableLocator()->get($t);
Expand All @@ -292,7 +292,7 @@ public function execute(Arguments $args, ConsoleIo $io): int

// Skip a table if already contains data
if($Model->find()->count() > 0) {
$modeltableEmpty = false;
$outboundTableEmpty = false;
$io->warning("Table (" . $t . ") is not empty. We will not overwrite existing data.");
}

Expand All @@ -303,12 +303,12 @@ public function execute(Arguments $args, ConsoleIo $io): int
!empty($selected)
&& !in_array($t, $selected)
) {
$notSelected = true;
$skipTableTransmogrification = true;
$io->warning("Skipping Transmogrification. Table ($t) is not in the selected subset.");
}

// Mark the table as skipped if it is not empty and not selected
$this->cache['skipInsert'][$outboundQualifiedTableName] = (!$modeltableEmpty && $notSelected);
$this->cache['skipInsert'][$outboundQualifiedTableName] = !$outboundTableEmpty || $skipTableTransmogrification;

/*
* End of checks
Expand Down Expand Up @@ -371,7 +371,7 @@ public function execute(Arguments $args, ConsoleIo $io): int
// and then skip insert from everywhere.
$this->runPreRowHook($t, $origRow, $row);

// Step 3: Set changelog defaults (created/modified timestamps, user IDs)
// Set changelog defaults (created/modified timestamps, user IDs)
// Must be done before boolean normalization as it adds new fields
$this->populateChangelogDefaults(
$t,
Expand Down Expand Up @@ -407,15 +407,15 @@ public function execute(Arguments $args, ConsoleIo $io): int
$warns++;
$this->cache['rejected'][$outboundQualifiedTableName][$row['id']] = $row;
$progress->warn("Skipping $t record " . $row['id'] . " due to invalid foreign key: " . $e->getMessage());
$io->ask('Press <enter> to continue...');
$progress->ask('Press <enter> to continue...');
}
catch(\InvalidArgumentException $e) {
// If we can't find a value for mapping we skip the record
// (ie: mapLegacyFieldNames basically requires a successful mapping)
$warns++;
$this->cache['rejected'][$outboundQualifiedTableName][$row['id']] = $row;
$progress->warn("Skipping $t record " . $row['id'] . ": " . $e->getMessage());
$io->ask('Press <enter> to continue...');
$progress->ask('Press <enter> to continue...');
}
catch(\Exception $e) {
$err++;
Expand Down
17 changes: 17 additions & 0 deletions app/plugins/Transmogrify/src/Lib/Traits/CacheTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ protected function findCoId(array $row): int

isset($row['group_id']) => $this->getCoIdFromGroupId((int)$row['group_id']),

isset($row['match_server_id']) => $this->getCoIdFromMatchServer((int)$row['match_server_id']),

// Legacy/preRow: org_identity_id follows the same External Identity path
isset($row['org_identity_id']) => $this->getCoIdFromExternalIdentityId((int)$row['org_identity_id']),

Expand Down Expand Up @@ -179,6 +181,21 @@ private function getCoIdFromExternalIdentityId(int $externalIdentityId): ?int
return $personId !== null ? $this->getCoIdFromPersonId($personId) : null;
}

/**
* Resolve a CO ID from a Match Server ID via cache.
*
* @param int $matchServerId Match Server ID to resolve
* @return int|null CO ID if found, null otherwise
* @since COmanage Registry v5.2.0
*/
private function getCoIdFromMatchServer(int $matchServerId): ?int
{
if (isset($this->cache['match_servers']['id'][$matchServerId]['server_id'])) {
$serverId = (int)$this->cache['match_servers']['id'][$matchServerId]['server_id'];
return $this->cache['servers']['id'][$serverId]['co_id'] ?? null;
}
return null;
}

/**
* Resolve a CO ID from a Person Role ID via cache.
Expand Down
38 changes: 38 additions & 0 deletions app/plugins/Transmogrify/src/Lib/Traits/TypeMapperTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ trait TypeMapperTrait
'SQ' => 'CoreServer.SqlServers',
];

/**
* Map match attribute types to corresponding model names
*
* @var array<string,string>
* @since COmanage Registry v5.2.0
*/
public const MATCH_ATTRIBUTE_TYPE_MAP = [
'emailAddress' => 'EmailAddresses',
'name' => 'Names',
'identifier' => 'Identifiers',
'dateOfBirth' => '',
];

/**
* Map address type to corresponding type ID
*
Expand Down Expand Up @@ -145,6 +158,31 @@ protected function mapIdentifierType(array $row): ?int
return $this->mapType($row, 'Identifiers.type', $this->findCoId($row));
}


/**
* Map match attribute type to corresponding type ID based on attribute key
*
* @param array $row Row data containing match attribute
* @return int|null Mapped type ID or null if attribute not found/mapped
* @since COmanage Registry v5.2.0
*/
protected function mapMatchAttributeTypeId(array $row): ?int
{
// Determine the model based on the match attribute map (eg, emailAddress -> EmailAddresses)
$attributeKey = $row['attribute'] ?? null;
if (!$attributeKey || !array_key_exists($attributeKey, self::MATCH_ATTRIBUTE_TYPE_MAP)) {
return null;
}

$model = self::MATCH_ATTRIBUTE_TYPE_MAP[$attributeKey];

// If the mapping is empty (eg, dateOfBirth), return null
if ($model === '') {
return null;
}
return $this->mapType($row, $model . '.type', $this->findCoId($row));
}

/**
* Map login identifiers, in accordance with the configuration.
*
Expand Down

0 comments on commit 0b70f08

Please sign in to comment.