Mirror cloud worker: empty-endpoint fallback + loopback rewrite

Three parity changes with appwrite-labs/cloud#4103:

1. Empty endpoint clause in $isLocalEndpoint so callers bypassing
   processMigration's defaulting (e.g. unit tests) still get the DB path
   when _APP_MIGRATION_HOST is configured.

2. resolveLocalEndpoint + getMigrationEndpoint helpers, with the inline
   construction in processMigration consolidated to use them.

3. Always rewrite loopback source URLs (localhost / 127.0.0.1 / etc.)
   to the internal host for SourceAppwrite, on both DB and SDK paths.
   Loopback addresses are unreachable from inside the worker container;
   the rewrite preserves legit dev scenarios. SDK auth still gates access.
This commit is contained in:
Prem Palanisamy
2026-05-23 14:40:12 +01:00
parent 7f5b0225b7
commit ced8594752
+42 -8
View File
@@ -216,7 +216,10 @@ class Migrations extends Action
$matchesDomain = $localDomain !== ''
&& ($sourceHost === $localDomain || str_ends_with((string) $sourceHost, '.' . $localDomain));
$matchesInternal = $migrationHost !== '' && $sourceHost === $migrationHost;
$isLocalEndpoint = is_string($sourceHost) && ($matchesDomain || $matchesInternal);
// Empty endpoint is defaulted to the internal host by processMigration, so
// treat it as local up-front for callers that bypass that defaulting (tests).
$isLocalEndpoint = (is_string($sourceHost) && ($matchesDomain || $matchesInternal))
|| (empty($credentials['endpoint']) && $migrationHost !== '');
$isLocalSource = !$this->sourceProject->isEmpty()
&& (!$isAppwriteToAppwrite || $isLocalEndpoint);
@@ -236,6 +239,14 @@ class Migrations extends Action
$queries = Query::parseQueries($migrationOptions['queries'] ?? []);
}
$sourceEndpoint = $credentials['endpoint'] ?? '';
if ($source === SourceAppwrite::getName()) {
// Loopback URLs are unreachable from inside the worker container — rewrite
// them to the internal host on both DB and SDK paths so the SDK fallback /
// primary call resolves. SDK auth (apiKey) still gates access on the SDK path.
$sourceEndpoint = $this->resolveLocalEndpoint($sourceEndpoint);
}
$migrationSource = match ($source) {
Firebase::getName() => new Firebase(
json_decode($credentials['serviceAccount'], true),
@@ -260,7 +271,7 @@ class Migrations extends Action
),
SourceAppwrite::getName() => new SourceAppwrite(
$credentials['projectId'],
$credentials['endpoint'],
$sourceEndpoint,
$credentials['apiKey'],
$getDatabasesDB,
$useAppwriteApiSource ? SourceAppwrite::SOURCE_API : SourceAppwrite::SOURCE_DATABASE,
@@ -470,12 +481,7 @@ class Migrations extends Action
$aggregatedResources = [];
$caughtError = null;
$host = System::getEnv('_APP_MIGRATION_HOST');
if (empty($host)) {
throw new \Exception('_APP_MIGRATION_HOST is not set');
}
$endpoint = 'http://' . $host . '/v1';
$endpoint = $this->getMigrationEndpoint();
try {
$credentials = $migration->getAttribute('credentials', []);
@@ -682,6 +688,34 @@ class Migrations extends Action
return ($this->getDatabasesDB)($database);
}
private function resolveLocalEndpoint(string $endpoint): string
{
if ($endpoint === '') {
return $this->getMigrationEndpoint();
}
$host = parse_url($endpoint, PHP_URL_HOST);
if (!is_string($host)) {
return $endpoint;
}
if (!in_array(strtolower($host), ['localhost', '127.0.0.1', '0.0.0.0', '::1'], true)) {
return $endpoint;
}
return $this->getMigrationEndpoint();
}
private function getMigrationEndpoint(): string
{
$host = System::getEnv('_APP_MIGRATION_HOST');
if (empty($host)) {
throw new \Exception('_APP_MIGRATION_HOST is not set');
}
return 'http://' . $host . '/v1';
}
/**
* Handle actions to be performed when a CSV export migration is successfully completed
*