appwrite/app/cli.php

342 lines
12 KiB
PHP
Raw Normal View History

2020-07-28 19:48:51 +00:00
<?php
2022-05-23 14:54:50 +00:00
require_once __DIR__ . '/init.php';
2020-07-28 19:48:51 +00:00
2022-12-13 11:16:12 +00:00
use Appwrite\Event\Certificate;
2024-03-06 17:34:21 +00:00
use Appwrite\Event\Delete;
2022-11-15 18:13:17 +00:00
use Appwrite\Event\Func;
2025-01-30 04:07:44 +00:00
use Appwrite\Event\StatsResources;
2025-01-30 04:53:53 +00:00
use Appwrite\Event\StatsUsage;
2022-11-14 10:29:10 +00:00
use Appwrite\Platform\Appwrite;
use Appwrite\Runtimes\Runtimes;
2025-11-05 01:41:15 +00:00
use Appwrite\Utopia\Database\Documents\User;
use Executor\Executor;
2025-05-19 10:40:11 +00:00
use Swoole\Runtime;
2025-05-14 06:16:54 +00:00
use Swoole\Timer;
use Utopia\Cache\Adapter\Pool as CachePool;
2025-04-17 05:09:08 +00:00
use Utopia\Cache\Adapter\Sharding;
2024-10-08 07:54:40 +00:00
use Utopia\Cache\Cache;
2022-11-14 10:46:11 +00:00
use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Adapter\Pool as DatabasePool;
2024-10-08 07:54:40 +00:00
use Utopia\Database\Database;
use Utopia\Database\Document;
2024-03-06 17:34:21 +00:00
use Utopia\Database\Validator\Authorization;
use Utopia\DI\Dependency;
2024-10-08 07:54:40 +00:00
use Utopia\DSN\DSN;
2022-10-28 08:56:45 +00:00
use Utopia\Logger\Log;
2024-03-06 17:34:21 +00:00
use Utopia\Platform\Service;
2024-10-08 07:54:40 +00:00
use Utopia\Pools\Group;
use Utopia\Queue\Broker\Pool as BrokerPool;
2025-01-29 14:13:58 +00:00
use Utopia\Queue\Publisher;
2022-10-28 08:49:05 +00:00
use Utopia\Registry\Registry;
2024-04-01 11:02:47 +00:00
use Utopia\System\System;
use Utopia\Telemetry\Adapter\None as NoTelemetry;
2025-05-14 06:16:54 +00:00
use function Swoole\Coroutine\run;
2022-04-10 09:38:22 +00:00
2025-03-14 12:53:34 +00:00
// overwriting runtimes to be architecture agnostic for CLI
2025-03-11 17:19:25 +00:00
Config::setParam('runtimes', (new Runtimes('v5'))->getAll(supported: false));
// require controllers after overwriting runtimes
require_once __DIR__ . '/controllers/general.php';
2026-02-03 04:19:37 +00:00
global $register;
2024-10-08 07:54:40 +00:00
$platform = new Appwrite();
$args = $platform->getEnv('argv');
2026-02-10 06:49:41 +00:00
\array_shift($args);
if (!isset($args[0])) {
Console::error('Missing task name');
Console::exit(1);
}
$taskName = $args[0];
$platform->init(Service::TYPE_TASK);
$cli = $platform->getCli();
$setResource = function (string $name, callable $callback, array $injections = []) use ($cli) {
$dependency = new Dependency();
$dependency->setName($name)->setCallback($callback);
foreach ($injections as $injection) {
$dependency->inject($injection);
}
$cli->setResource($dependency);
};
$setResource('register', fn () => $register, []);
$setResource('cache', function ($pools) {
2024-10-08 07:54:40 +00:00
$list = Config::getParam('pools-cache', []);
$adapters = [];
foreach ($list as $value) {
$adapters[] = new CachePool($pools->get($value));
2024-10-08 07:54:40 +00:00
}
return new Cache(new Sharding($adapters));
}, ['pools']);
$setResource('pools', function (Registry $register) {
2024-10-08 07:54:40 +00:00
return $register->get('pools');
}, ['register']);
$setResource('authorization', function () {
$authorization = new Authorization();
$authorization->disable();
return $authorization;
}, []);
$setResource('dbForPlatform', function ($pools, $cache, $authorization) {
2024-10-08 07:54:40 +00:00
$sleep = 3;
$maxAttempts = 5;
$attempts = 0;
$ready = false;
do {
$attempts++;
try {
// Prepare database connection
$adapter = new DatabasePool($pools->get('console'));
$dbForPlatform = new Database($adapter, $cache);
2024-10-08 07:54:40 +00:00
$dbForPlatform
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
2024-10-08 07:54:40 +00:00
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', 'console');
2025-11-05 01:41:15 +00:00
$dbForPlatform->setDocumentType('users', User::class);
2024-10-08 07:54:40 +00:00
// Ensure tables exist
$collections = Config::getParam('collections', [])['console'];
$last = \array_key_last($collections);
if (!($dbForPlatform->exists($dbForPlatform->getDatabase(), $last))) { /** TODO cache ready variable using registry */
2024-10-08 07:54:40 +00:00
throw new Exception('Tables not ready yet.');
}
2022-10-28 08:56:45 +00:00
2024-10-08 07:54:40 +00:00
$ready = true;
} catch (\Throwable $err) {
Console::warning($err->getMessage());
sleep($sleep);
}
} while ($attempts < $maxAttempts && !$ready);
if (!$ready) {
throw new Exception("Console is not ready yet. Please try again later.");
}
return $dbForPlatform;
}, ['pools', 'cache', 'authorization']);
2024-10-08 07:54:40 +00:00
$setResource('console', function () {
return new Document(Config::getParam('console'));
}, []);
$setResource(
2025-10-09 10:59:32 +00:00
'isResourceBlocked',
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false,
[]
2025-10-09 10:59:32 +00:00
);
$setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, $authorization) {
2024-10-08 07:54:40 +00:00
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases) {
2024-10-08 07:54:40 +00:00
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
2024-10-08 07:54:40 +00:00
}
try {
$dsn = new DSN($project->getAttribute('database'));
} catch (\InvalidArgumentException) {
// TODO: Temporary until all projects are using shared tables
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
if (isset($databases[$dsn->getHost()])) {
$database = $databases[$dsn->getHost()];
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
2024-10-08 07:54:40 +00:00
if (\in_array($dsn->getHost(), $sharedTables)) {
2024-10-08 07:54:40 +00:00
$database
->setSharedTables(true)
2025-06-16 17:24:48 +00:00
->setTenant((int)$project->getSequence())
2024-10-08 07:54:40 +00:00
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
2025-05-26 05:42:11 +00:00
->setNamespace('_' . $project->getSequence());
2024-10-01 14:30:47 +00:00
}
2022-10-28 08:56:45 +00:00
2024-10-08 07:54:40 +00:00
return $database;
}
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
2024-10-08 07:54:40 +00:00
$databases[$dsn->getHost()] = $database;
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
2024-10-08 07:54:40 +00:00
if (\in_array($dsn->getHost(), $sharedTables)) {
2024-10-08 07:54:40 +00:00
$database
->setSharedTables(true)
2025-06-16 17:24:48 +00:00
->setTenant((int)$project->getSequence())
2024-10-08 07:54:40 +00:00
->setNamespace($dsn->getParam('namespace'));
} else {
$database
->setSharedTables(false)
->setTenant(null)
2025-05-26 05:42:11 +00:00
->setNamespace('_' . $project->getSequence());
2024-10-08 07:54:40 +00:00
}
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
2024-10-08 07:54:40 +00:00
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId());
return $database;
};
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
2024-10-08 07:54:40 +00:00
$setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
2025-01-27 02:26:06 +00:00
$database = null;
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
2025-01-27 02:26:06 +00:00
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
2025-06-16 17:24:48 +00:00
$database->setTenant((int)$project->getSequence());
2025-01-27 02:26:06 +00:00
return $database;
}
$adapter = new DatabasePool($pools->get('logs'));
$database = new Database($adapter, $cache);
2025-01-27 02:26:06 +00:00
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
2025-01-27 02:26:06 +00:00
->setSharedTables(true)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_TASK)
2025-01-27 02:26:06 +00:00
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
// set tenant
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
2025-06-16 17:24:48 +00:00
$database->setTenant((int)$project->getSequence());
2025-01-27 02:26:06 +00:00
}
return $database;
};
}, ['pools', 'cache', 'authorization']);
$setResource('publisher', function (Group $pools) {
2025-07-03 18:24:40 +00:00
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
$setResource('publisherDatabases', function (BrokerPool $publisher) {
2025-07-16 17:11:04 +00:00
return $publisher;
}, ['publisher']);
$setResource('publisherFunctions', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
$setResource('publisherMigrations', function (BrokerPool $publisher) {
2025-07-16 17:11:04 +00:00
return $publisher;
}, ['publisher']);
$setResource('publisherStatsUsage', function (BrokerPool $publisher) {
2025-07-16 17:11:04 +00:00
return $publisher;
}, ['publisher']);
$setResource('publisherMessaging', function (BrokerPool $publisher) {
return $publisher;
}, ['publisher']);
$setResource('queueForStatsUsage', function (Publisher $publisher) {
return new StatsUsage($publisher);
}, ['publisher']);
$setResource('queueForStatsResources', function (Publisher $publisher) {
return new StatsResources($publisher);
}, ['publisher']);
$setResource('queueForFunctions', function (Publisher $publisher) {
2025-01-29 14:13:58 +00:00
return new Func($publisher);
}, ['publisher']);
$setResource('queueForDeletes', function (Publisher $publisher) {
2025-01-29 14:13:58 +00:00
return new Delete($publisher);
}, ['publisher']);
$setResource('queueForCertificates', function (Publisher $publisher) {
2025-01-29 14:13:58 +00:00
return new Certificate($publisher);
}, ['publisher']);
$setResource('logError', function (Registry $register) {
2024-10-08 07:54:40 +00:00
return function (Throwable $error, string $namespace, string $action) use ($register) {
2025-04-14 21:35:26 +00:00
Console::error('[Error] Timestamp: ' . date('c', time()));
Console::error('[Error] Type: ' . get_class($error));
Console::error('[Error] Message: ' . $error->getMessage());
Console::error('[Error] File: ' . $error->getFile());
Console::error('[Error] Line: ' . $error->getLine());
Console::error('[Error] Trace: ' . $error->getTraceAsString());
2025-04-14 21:35:26 +00:00
2024-10-08 07:54:40 +00:00
$logger = $register->get('logger');
if ($logger) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
$log = new Log();
$log->setNamespace($namespace);
2024-11-26 13:54:27 +00:00
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
2024-10-08 07:54:40 +00:00
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$log->addTag('code', $error->getCode());
$log->addTag('verboseType', get_class($error));
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
2025-04-14 21:35:26 +00:00
$log->addExtra('detailedTrace', $error->getTrace());
2024-10-08 07:54:40 +00:00
2026-01-01 10:17:20 +00:00
if ($error->getPrevious() !== null) {
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
$log->addExtra('previousMessage', $error->getPrevious()->getMessage());
}
$log->addExtra('previousFile', $error->getPrevious()->getFile());
$log->addExtra('previousLine', $error->getPrevious()->getLine());
}
2024-10-08 07:54:40 +00:00
$log->setAction($action);
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
try {
$responseCode = $logger->addLog($log);
Console::info('Error log pushed with status code: ' . $responseCode);
} catch (Throwable $th) {
Console::error('Error pushing log: ' . $th->getMessage());
}
}
};
}, ['register']);
2022-10-28 08:56:45 +00:00
$setResource('executor', fn () => new Executor(), []);
2025-04-14 21:35:26 +00:00
$setResource('telemetry', fn () => new NoTelemetry(), []);
2022-11-16 15:28:15 +00:00
$cli
->error()
->inject('error')
2025-04-14 21:35:26 +00:00
->inject('logError')
->action(function (Throwable $error, callable $logError) use ($taskName) {
call_user_func_array($logError, [
$error,
'Task',
$taskName,
]);
Timer::clearAll();
2022-11-16 15:28:15 +00:00
});
$cli->shutdown()->action(fn () => Timer::clearAll());
2025-05-21 14:21:11 +00:00
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
require_once __DIR__ . '/init/span.php';
run($cli->run(...));