Move settings migration to source adapter via console API key

This commit is contained in:
Prem Palanisamy
2026-02-12 15:26:29 +00:00
parent b338ed9a2c
commit 01958a0f4e
+27 -140
View File
@@ -28,8 +28,6 @@ use Utopia\Migration\Destinations\CSV as DestinationCSV;
use Utopia\Migration\Exception as MigrationException;
use Utopia\Migration\Resource;
use Utopia\Migration\Resources\Database\Database as ResourceDatabase;
use Utopia\Migration\Resources\Settings\Key as ResourceKey;
use Utopia\Migration\Resources\Settings\Platform as ResourcePlatform;
use Utopia\Migration\Resources\Database\Row as ResourceRow;
use Utopia\Migration\Resources\Database\Table as ResourceTable;
use Utopia\Migration\Source;
@@ -216,6 +214,8 @@ class Migrations extends Action
$dataSource,
$database,
$queries,
$credentials['consoleApiKey'] ?? '',
$credentials['projectId'] ?? '',
),
CSV::getName() => new CSV(
$resourceId,
@@ -325,6 +325,29 @@ class Migrations extends Action
return API_KEY_DYNAMIC . '_' . $apiKey;
}
/**
* Generate a console-scoped dynamic API key for settings migration.
*
* This key allows the source adapter to read platforms and keys
* via the console API endpoints (same-instance only).
*
* @throws Exception
*/
protected function generateConsoleAPIKey(): string
{
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 86400, 0);
$apiKey = $jwt->encode([
'projectId' => 'console',
'scopes' => [
'platforms.read',
'keys.read',
],
]);
return API_KEY_DYNAMIC . '_' . $apiKey;
}
/**
* @throws AuthorizationException
* @throws Conflict
@@ -344,6 +367,7 @@ class Migrations extends Action
$project = $this->project;
$tempAPIKey = $this->generateAPIKey($project);
$tempConsoleAPIKey = $this->generateConsoleAPIKey();
$transfer = $source = $destination = null;
@@ -361,6 +385,7 @@ class Migrations extends Action
$credentials['projectId'] = $credentials['projectId'] ?? $project->getId();
$credentials['apiKey'] = $credentials['apiKey'] ?? $tempAPIKey;
$credentials['endpoint'] = $credentials['endpoint'] ?? $endpoint;
$credentials['consoleApiKey'] = $credentials['consoleApiKey'] ?? $tempConsoleAPIKey;
}
if ($migration->getAttribute('destination') === DestinationAppwrite::getName()) {
@@ -430,8 +455,6 @@ class Migrations extends Action
$migration->getAttribute('resourceType')
);
$this->migrateSettings($migration, $destination);
$destination->shutdown();
$source->shutdown();
}
@@ -520,142 +543,6 @@ class Migrations extends Action
}
}
/**
* Migrate platforms and API keys at the worker level.
*
* Settings (platforms, keys) live in dbForPlatform, not dbForProject.
* Instead of passing dbForPlatform to the source adapter, the worker
* reads from the source project and writes via the destination adapter.
*/
protected function migrateSettings(Document $migration, Destination $destination): void
{
if (!($destination instanceof DestinationAppwrite)) {
return;
}
$resources = $migration->getAttribute('resources', []);
$credentials = $migration->getAttribute('credentials', []);
$hasPlatforms = \in_array(Resource::TYPE_PLATFORM, $resources);
$hasKeys = \in_array(Resource::TYPE_KEY, $resources);
if (!$hasPlatforms && !$hasKeys) {
return;
}
$sourceProjectId = $credentials['projectId'] ?? '';
if (empty($sourceProjectId)) {
return;
}
$sourceProject = $this->dbForPlatform->getDocument('projects', $sourceProjectId);
if ($sourceProject->isEmpty()) {
return;
}
$sourceInternalId = $sourceProject->getSequence();
if ($hasPlatforms) {
$this->migratePlatforms($sourceInternalId, $destination);
}
if ($hasKeys) {
$this->migrateKeys($sourceInternalId, $destination);
}
}
/**
* Read platforms from source project and import via destination.
*/
protected function migratePlatforms(string $sourceInternalId, DestinationAppwrite $destination): void
{
$lastDocument = null;
$batchSize = 100;
while (true) {
$queries = [
Query::equal('projectInternalId', [$sourceInternalId]),
Query::limit($batchSize),
];
if ($lastDocument !== null) {
$queries[] = Query::cursorAfter($lastDocument);
}
$platforms = $this->dbForPlatform->find('platforms', $queries);
if (empty($platforms)) {
break;
}
foreach ($platforms as $platform) {
$resource = new ResourcePlatform(
'unique()',
$platform->getAttribute('type', ''),
$platform->getAttribute('name', ''),
$platform->getAttribute('key', ''),
$platform->getAttribute('store', ''),
$platform->getAttribute('hostname', ''),
);
$resource->setPermissions($platform->getPermissions());
$destination->importSettingsResource($resource);
}
$lastDocument = \end($platforms);
if (\count($platforms) < $batchSize) {
break;
}
}
}
/**
* Read API keys from source project and import via destination.
*/
protected function migrateKeys(string $sourceInternalId, DestinationAppwrite $destination): void
{
$lastDocument = null;
$batchSize = 100;
while (true) {
$queries = [
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$sourceInternalId]),
Query::limit($batchSize),
];
if ($lastDocument !== null) {
$queries[] = Query::cursorAfter($lastDocument);
}
$keys = $this->dbForPlatform->find('keys', $queries);
if (empty($keys)) {
break;
}
foreach ($keys as $key) {
$resource = new ResourceKey(
'unique()',
$key->getAttribute('name', ''),
$key->getAttribute('scopes', []),
$key->getAttribute('secret', ''),
$key->getAttribute('expire', null),
$key->getAttribute('accessedAt', null),
$key->getAttribute('sdks', []),
);
$resource->setPermissions($key->getPermissions());
$destination->importSettingsResource($resource);
}
$lastDocument = \end($keys);
if (\count($keys) < $batchSize) {
break;
}
}
}
/**
* Handle actions to be performed when a CSV export migration is successfully completed
*