mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge pull request #12226 from appwrite/spike/isPublishable-migration
Stop user-data errors leaking to Sentry from migrations worker (isPublishable)
This commit is contained in:
@@ -1246,6 +1246,26 @@ return [
|
||||
'description' => 'The specified database type is not supported for CSV import or export operations.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::MIGRATION_SOURCE_PROJECT_ID_REQUIRED => [
|
||||
'name' => Exception::MIGRATION_SOURCE_PROJECT_ID_REQUIRED,
|
||||
'description' => 'A source projectId is required for Appwrite migrations. Provide it in the migration credentials.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::MIGRATION_SOURCE_PROJECT_NOT_FOUND => [
|
||||
'name' => Exception::MIGRATION_SOURCE_PROJECT_NOT_FOUND,
|
||||
'description' => 'The source project for the provided projectId was not found. Verify the projectId and the API key has access to it.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::MIGRATION_SOURCE_TYPE_INVALID => [
|
||||
'name' => Exception::MIGRATION_SOURCE_TYPE_INVALID,
|
||||
'description' => 'The migration source type is invalid. Use one of the supported source types.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::MIGRATION_DESTINATION_TYPE_INVALID => [
|
||||
'name' => Exception::MIGRATION_DESTINATION_TYPE_INVALID,
|
||||
'description' => 'The migration destination type is invalid. Use one of the supported destination types.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Realtime */
|
||||
Exception::REALTIME_MESSAGE_FORMAT_INVALID => [
|
||||
|
||||
Generated
+55
-29
@@ -2641,16 +2641,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
"version": "v3.6.0",
|
||||
"version": "v3.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/deprecation-contracts.git",
|
||||
"reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
|
||||
"reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
|
||||
"reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
|
||||
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b",
|
||||
"reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2663,7 +2663,7 @@
|
||||
"name": "symfony/contracts"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-main": "3.6-dev"
|
||||
"dev-main": "3.7-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -2688,7 +2688,7 @@
|
||||
"description": "A generic function and convention to trigger deprecation notices",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
|
||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2699,12 +2699,16 @@
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-25T14:21:43+00:00"
|
||||
"time": "2026-04-13T15:52:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client",
|
||||
@@ -2809,16 +2813,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client-contracts",
|
||||
"version": "v3.6.0",
|
||||
"version": "v3.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-client-contracts.git",
|
||||
"reference": "75d7043853a42837e68111812f4d964b01e5101c"
|
||||
"reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c",
|
||||
"reference": "75d7043853a42837e68111812f4d964b01e5101c",
|
||||
"url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d",
|
||||
"reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2831,7 +2835,7 @@
|
||||
"name": "symfony/contracts"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-main": "3.6-dev"
|
||||
"dev-main": "3.7-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -2867,7 +2871,7 @@
|
||||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0"
|
||||
"source": "https://github.com/symfony/http-client-contracts/tree/v3.7.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2878,12 +2882,16 @@
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-04-29T11:18:49+00:00"
|
||||
"time": "2026-03-06T13:17:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
@@ -3212,16 +3220,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
"version": "v3.6.1",
|
||||
"version": "v3.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/service-contracts.git",
|
||||
"reference": "45112560a3ba2d715666a509a0bc9521d10b6c43"
|
||||
"reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43",
|
||||
"reference": "45112560a3ba2d715666a509a0bc9521d10b6c43",
|
||||
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a",
|
||||
"reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3239,7 +3247,7 @@
|
||||
"name": "symfony/contracts"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-main": "3.6-dev"
|
||||
"dev-main": "3.7-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -3275,7 +3283,7 @@
|
||||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/service-contracts/tree/v3.6.1"
|
||||
"source": "https://github.com/symfony/service-contracts/tree/v3.7.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3295,7 +3303,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-15T11:30:57+00:00"
|
||||
"time": "2026-03-28T09:44:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "tbachert/spi",
|
||||
@@ -4531,16 +4539,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/migration",
|
||||
"version": "1.10.0",
|
||||
"version": "1.10.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/migration.git",
|
||||
"reference": "55f4863d690e775f44fec3cae4bd1f4491fed5ea"
|
||||
"reference": "759d6d61b327313cbeeeb4ea0c3e2459164b4827"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/55f4863d690e775f44fec3cae4bd1f4491fed5ea",
|
||||
"reference": "55f4863d690e775f44fec3cae4bd1f4491fed5ea",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/759d6d61b327313cbeeeb4ea0c3e2459164b4827",
|
||||
"reference": "759d6d61b327313cbeeeb4ea0c3e2459164b4827",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4566,7 +4574,25 @@
|
||||
"Utopia\\Migration\\": "src/Migration"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Utopia\\Tests\\": "tests/Migration"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": [
|
||||
"./vendor/bin/phpunit"
|
||||
],
|
||||
"lint": [
|
||||
"./vendor/bin/pint --test"
|
||||
],
|
||||
"format": [
|
||||
"./vendor/bin/pint"
|
||||
],
|
||||
"check": [
|
||||
"./vendor/bin/phpstan analyse --level 3 src tests --memory-limit 2G"
|
||||
]
|
||||
},
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
@@ -4579,10 +4605,10 @@
|
||||
"utopia"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/migration/issues",
|
||||
"source": "https://github.com/utopia-php/migration/tree/1.10.0"
|
||||
"source": "https://github.com/utopia-php/migration/tree/1.10.1",
|
||||
"issues": "https://github.com/utopia-php/migration/issues"
|
||||
},
|
||||
"time": "2026-05-06T04:35:32+00:00"
|
||||
"time": "2026-05-07T07:23:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/mongo",
|
||||
|
||||
@@ -348,6 +348,10 @@ class Exception extends \Exception
|
||||
public const string MIGRATION_IN_PROGRESS = 'migration_in_progress';
|
||||
public const string MIGRATION_PROVIDER_ERROR = 'migration_provider_error';
|
||||
public const string MIGRATION_DATABASE_TYPE_UNSUPPORTED = 'migration_database_type_unsupported';
|
||||
public const string MIGRATION_SOURCE_PROJECT_ID_REQUIRED = 'migration_source_project_id_required';
|
||||
public const string MIGRATION_SOURCE_PROJECT_NOT_FOUND = 'migration_source_project_not_found';
|
||||
public const string MIGRATION_SOURCE_TYPE_INVALID = 'migration_source_type_invalid';
|
||||
public const string MIGRATION_DESTINATION_TYPE_INVALID = 'migration_destination_type_invalid';
|
||||
|
||||
/** Realtime */
|
||||
public const string REALTIME_MESSAGE_FORMAT_INVALID = 'realtime_message_format_invalid';
|
||||
|
||||
@@ -197,13 +197,13 @@ class Migrations extends Action
|
||||
$projectDB = null;
|
||||
$useAppwriteApiSource = false;
|
||||
if ($source === SourceAppwrite::getName() && empty($credentials['projectId'])) {
|
||||
throw new \Exception('Source projectId is required for Appwrite migrations');
|
||||
throw new Exception(Exception::MIGRATION_SOURCE_PROJECT_ID_REQUIRED);
|
||||
}
|
||||
|
||||
if (! empty($credentials['projectId'])) {
|
||||
$this->sourceProject = $this->dbForPlatform->getDocument('projects', $credentials['projectId']);
|
||||
if ($this->sourceProject->isEmpty()) {
|
||||
throw new \Exception('Source project not found for provided projectId');
|
||||
throw new Exception(Exception::MIGRATION_SOURCE_PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$sourceRegion = $this->sourceProject->getAttribute('region', 'default');
|
||||
@@ -266,7 +266,7 @@ class Migrations extends Action
|
||||
$this->deviceForMigrations,
|
||||
$this->dbForProject,
|
||||
),
|
||||
default => throw new \Exception('Invalid source type'),
|
||||
default => throw new Exception(Exception::MIGRATION_SOURCE_TYPE_INVALID),
|
||||
};
|
||||
|
||||
$resources = $migration->getAttribute('resources', []);
|
||||
@@ -312,7 +312,7 @@ class Migrations extends Action
|
||||
$options['filename'],
|
||||
$options['columns'] ?? [],
|
||||
),
|
||||
default => throw new \Exception('Invalid destination type'),
|
||||
default => throw new Exception(Exception::MIGRATION_DESTINATION_TYPE_INVALID),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -438,6 +438,7 @@ class Migrations extends Action
|
||||
|
||||
$transfer = $source = $destination = null;
|
||||
$aggregatedResources = [];
|
||||
$caughtError = null;
|
||||
|
||||
$host = System::getEnv('_APP_MIGRATION_HOST');
|
||||
if (empty($host)) {
|
||||
@@ -531,7 +532,6 @@ class Migrations extends Action
|
||||
if (!empty($sourceErrors) || ! empty($destinationErrors)) {
|
||||
$migration->setAttribute('status', 'failed');
|
||||
$migration->setAttribute('stage', 'finished');
|
||||
$migration->setAttribute('errors', $this->sanitizeErrors($sourceErrors, $destinationErrors));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -546,35 +546,69 @@ class Migrations extends Action
|
||||
$migration->setAttribute('status', 'failed');
|
||||
$migration->setAttribute('stage', 'finished');
|
||||
|
||||
call_user_func($this->logError, $th, 'appwrite-worker', 'appwrite-queue-' . self::getName(), [
|
||||
'migrationId' => $migration->getId(),
|
||||
'source' => $migration->getAttribute('source') ?? '',
|
||||
'destination' => $migration->getAttribute('destination') ?? '',
|
||||
]);
|
||||
$caughtError = $th;
|
||||
|
||||
// Mirror general.php's HTTP-error pattern: typed AppwriteException uses its
|
||||
// registry-driven isPublishable() flag; library-thrown Migration\Exception is
|
||||
// always user-facing; anything else is unknown and surfaced to Sentry.
|
||||
if ($th instanceof Exception) {
|
||||
$publish = $th->isPublishable();
|
||||
} elseif ($th instanceof MigrationException) {
|
||||
$publish = false;
|
||||
} else {
|
||||
$publish = true;
|
||||
}
|
||||
|
||||
if ($publish) {
|
||||
call_user_func($this->logError, $th, 'appwrite-worker', 'appwrite-queue-' . self::getName(), [
|
||||
'migrationId' => $migration->getId(),
|
||||
'source' => $migration->getAttribute('source') ?? '',
|
||||
'destination' => $migration->getAttribute('destination') ?? '',
|
||||
]);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
$sourceErrors = $source?->getErrors() ?? [];
|
||||
$destinationErrors = $destination?->getErrors() ?? [];
|
||||
|
||||
if ($caughtError !== null) {
|
||||
if ($caughtError instanceof MigrationException) {
|
||||
// library-thrown, message constructed by us
|
||||
$bubbled = $caughtError;
|
||||
} elseif ($caughtError instanceof Exception) {
|
||||
// typed AppwriteException — message comes from the curated registry
|
||||
$bubbled = new MigrationException(
|
||||
resourceName: '',
|
||||
resourceGroup: '',
|
||||
message: $caughtError->getMessage(),
|
||||
code: $caughtError->getCode(),
|
||||
previous: $caughtError,
|
||||
);
|
||||
} else {
|
||||
// unknown throwable — raw message may embed internal hostnames,
|
||||
// DSNs, tokens, etc. Replace with a generic user-facing string;
|
||||
// the original is preserved on `previous:` for Sentry.
|
||||
$bubbled = new MigrationException(
|
||||
resourceName: '',
|
||||
resourceGroup: '',
|
||||
message: 'Migration failed due to an unexpected error.',
|
||||
code: $caughtError->getCode() ?: 500,
|
||||
previous: $caughtError,
|
||||
);
|
||||
}
|
||||
$destinationErrors[] = $bubbled;
|
||||
}
|
||||
|
||||
$migration->setAttribute('errors', $this->sanitizeErrors(
|
||||
$sourceErrors,
|
||||
$destinationErrors,
|
||||
));
|
||||
|
||||
$this->updateMigrationDocument($migration, $project, $queueForRealtime);
|
||||
|
||||
if ($migration->getAttribute('status', '') === 'failed') {
|
||||
Console::error('Migration(' . $migration->getSequence() . ':' . $migration->getId() . ') failed, Project(' . $this->project->getSequence() . ':' . $this->project->getId() . ')');
|
||||
|
||||
$sourceErrors = $source?->getErrors() ?? [];
|
||||
$destinationErrors = $destination?->getErrors() ?? [];
|
||||
|
||||
foreach ([...$sourceErrors, ...$destinationErrors] as $error) {
|
||||
/** @var MigrationException $error */
|
||||
if ($error->getCode() === 0 || $error->getCode() >= 500) {
|
||||
($this->logError)($error, 'appwrite-worker', 'appwrite-queue-' . self::getName(), [
|
||||
'migrationId' => $migration->getId(),
|
||||
'source' => $migration->getAttribute('source') ?? '',
|
||||
'destination' => $migration->getAttribute('destination') ?? '',
|
||||
'resourceName' => $error->getResourceName(),
|
||||
'resourceGroup' => $error->getResourceGroup(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$source?->error();
|
||||
$destination?->error();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user