Merge branch 'feat-db-pools' into refactor-scheduler-improvements

This commit is contained in:
Matej Baco 2022-11-15 11:28:54 +01:00
commit 30e39f0bcf
51 changed files with 1387 additions and 1870 deletions

20
.env
View file

@ -23,6 +23,7 @@ _APP_DB_SCHEMA=appwrite
_APP_DB_USER=user
_APP_DB_PASS=password
_APP_DB_ROOT_PASS=rootsecretpassword
_APP_DB_MAX_CONNECTIONS=251
_APP_CONNECTIONS_DB_PROJECT=db_fra1_02=mariadb://user:password@mariadb:3306/appwrite
_APP_CONNECTIONS_DB_CONSOLE=db_fra1_01=mariadb://user:password@mariadb:3306/appwrite
_APP_CONNECTIONS_CACHE=redis_fra1_01=redis://redis:6379
@ -68,14 +69,14 @@ _APP_STORAGE_PREVIEW_LIMIT=20000000
_APP_FUNCTIONS_SIZE_LIMIT=30000000
_APP_FUNCTIONS_TIMEOUT=900
_APP_FUNCTIONS_BUILD_TIMEOUT=900
_APP_FUNCTIONS_CONTAINERS=10
_APP_FUNCTIONS_CPUS=0
_APP_FUNCTIONS_MEMORY=0
_APP_FUNCTIONS_MEMORY_SWAP=0
_APP_FUNCTIONS_INACTIVE_THRESHOLD=60
OPEN_RUNTIMES_NETWORK=appwrite_runtimes
_APP_FUNCTIONS_CPUS=1
_APP_FUNCTIONS_MEMORY=512
_APP_FUNCTIONS_INACTIVE_THRESHOLD=600
_APP_FUNCTIONS_RUNTIMES_NETWORK=openruntimes-runtimes
_APP_FUNCTIONS_CONNECTION_STORAGE=file://localhost
_APP_EXECUTOR_SECRET=your-secret-key
_APP_EXECUTOR_HOST=http://appwrite-executor/v1
_APP_EXECUTOR_HOST=http://exc1/v1
_APP_FUNCTIONS_RUNTIMES=
_APP_MAINTENANCE_INTERVAL=86400
_APP_MAINTENANCE_RETENTION_CACHE=2592000
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600
@ -87,6 +88,5 @@ _APP_USAGE_STATS=enabled
_APP_LOGGING_PROVIDER=
_APP_LOGGING_CONFIG=
_APP_REGION=nyc1
DOCKERHUB_PULL_USERNAME=
DOCKERHUB_PULL_PASSWORD=
DOCKERHUB_PULL_EMAIL=
_APP_DOCKER_HUB_USERNAME=
_APP_DOCKER_HUB_PASSWORD=

View file

@ -1,3 +1,10 @@
# TBD
- Replace Appwrite executor with OpenRuntimes Executor [#4650](https://github.com/appwrite/appwrite/pull/4650)
- Add `_APP_DB_MAX_CONNECTIONS` env var [#4673](https://github.com/appwrite/appwrite/pull/4673)
- Update Traefik 2.7 -> 2.9 [#4673](https://github.com/appwrite/appwrite/pull/4673)
- Increase Traefik TCP + file limits [#4673](https://github.com/appwrite/appwrite/pull/4673)
# Version 1.1.0
## Bugs

View file

@ -246,13 +246,10 @@ ENV _APP_SERVER=swoole \
_APP_SMS_FROM= \
_APP_FUNCTIONS_SIZE_LIMIT=30000000 \
_APP_FUNCTIONS_TIMEOUT=900 \
_APP_FUNCTIONS_CONTAINERS=10 \
_APP_FUNCTIONS_CPUS=1 \
_APP_FUNCTIONS_MEMORY=128 \
_APP_FUNCTIONS_MEMORY_SWAP=128 \
_APP_EXECUTOR_SECRET=a-random-secret \
_APP_EXECUTOR_HOST=http://appwrite-executor/v1 \
_APP_EXECUTOR_RUNTIME_NETWORK=appwrite_runtimes \
_APP_EXECUTOR_HOST=http://exc1/v1 \
_APP_SETUP=self-hosted \
_APP_VERSION=$VERSION \
_APP_USAGE_STATS=enabled \
@ -349,7 +346,6 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/install && \
chmod +x /usr/local/bin/migrate && \
chmod +x /usr/local/bin/realtime && \
chmod +x /usr/local/bin/executor && \
chmod +x /usr/local/bin/schedule && \
chmod +x /usr/local/bin/sdks && \
chmod +x /usr/local/bin/specs && \

View file

@ -3,21 +3,80 @@
require_once __DIR__ . '/init.php';
require_once __DIR__ . '/controllers/general.php';
use Utopia\App;
use Appwrite\Platform\Appwrite;
use Utopia\CLI\CLI;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Service;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use InfluxDB\Database as InfluxDatabase;
use Utopia\Logger\Log;
use Utopia\Pools\Group;
use Utopia\Registry\Registry;
function getInfluxDB(): InfluxDatabase
{
global $register;
Authorization::disable();
CLI::setResource('register', fn()=>$register);
CLI::setResource('cache', function ($pools) {
$list = Config::getParam('pools-cache', []);
$adapters = [];
foreach ($list as $value) {
$adapters[] = $pools
->get($value)
->pop()
->getResource()
;
}
return new Cache(new Sharding($adapters));
}, ['pools']);
CLI::setResource('pools', function (Registry $register) {
return $register->get('pools');
}, ['register']);
CLI::setResource('dbForConsole', function ($pools, $cache) {
$dbAdapter = $pools
->get('console')
->pop()
->getResource()
;
$database = new Database($dbAdapter, $cache);
$database->setNamespace('console');
return $database;
}, ['pools', 'cache']);
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForConsole, $cache) {
$getProjectDB = function (Document $project) use ($pools, $dbForConsole, $cache) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForConsole;
}
$dbAdapter = $pools
->get($project->getAttribute('database'))
->pop()
->getResource()
;
$database = new Database($dbAdapter, $cache);
$database->setNamespace('_' . $project->getInternalId());
return $database;
};
return $getProjectDB;
}, ['pools', 'dbForConsole', 'cache']);
CLI::setResource('influxdb', function (Registry $register) {
$client = $register->get('influxdb'); /** @var InfluxDB\Client $client */
$attempts = 0;
$max = 10;
@ -39,104 +98,46 @@ function getInfluxDB(): InfluxDatabase
}
} while ($attempts < $max);
return $database;
}
}, ['register']);
function getConsoleDB(): Database
{
global $register;
CLI::setResource('logError', function (Registry $register) {
return function (Throwable $error, string $namespace, string $action) use ($register) {
$logger = $register->get('logger');
$pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */
if ($logger) {
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
$dbAdapter = $pools
->get('console')
->pop()
->getResource()
;
$log = new Log();
$log->setNamespace($namespace);
$log->setServer(\gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$database = new Database($dbAdapter, getCache());
$log->addTag('code', $error->getCode());
$log->addTag('verboseType', get_class($error));
$database->setNamespace('console');
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
return $database;
}
$log->setAction($action);
/**
* Get internal project database
* @param Document $project
* @return Database
*/
function getProjectDB(Document $project): Database
{
global $register;
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
$pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */
if ($project->isEmpty() || $project->getId() === 'console') {
return getConsoleDB();
}
$dbAdapter = $pools
->get($project->getAttribute('database'))
->pop()
->getResource()
;
$database = new Database($dbAdapter, getCache());
$database->setNamespace('_' . $project->getInternalId());
return $database;
}
function getCache(): Cache
{
global $register;
$pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */
$list = Config::getParam('pools-cache', []);
$adapters = [];
foreach ($list as $value) {
$adapters[] = $pools
->get($value)
->pop()
->getResource()
;
}
return new Cache(new Sharding($adapters));
}
Authorization::disable();
$cli = new CLI();
include 'tasks/doctor.php';
include 'tasks/maintenance.php';
include 'tasks/volume-sync.php';
include 'tasks/install.php';
include 'tasks/migrate.php';
include 'tasks/sdks.php';
include 'tasks/specs.php';
include 'tasks/ssl.php';
include 'tasks/vars.php';
include 'tasks/usage.php';
include 'tasks/schedule.php';
$cli
->task('version')
->desc('Get the server version')
->action(function () {
Console::log(App::getEnv('_APP_VERSION', 'UNKNOWN'));
});
$cli
->error(function ($error) {
if (App::getEnv('_APP_ENV', 'development')) {
Console::error($error);
} else {
Console::error($error->getMessage());
$responseCode = $logger->addLog($log);
Console::info('Usage stats log pushed with status code: ' . $responseCode);
}
});
Console::warning("Failed: {$error->getMessage()}");
Console::warning($error->getTraceAsString());
};
}, ['register']);
$platform = new Appwrite();
$platform->init(Service::TYPE_CLI);
$cli = $platform->getCli();
$cli->run();

View file

@ -108,6 +108,19 @@ return [
'optional' => false,
'icon' => '',
],
'project' => [
'key' => 'project',
'name' => 'Project',
'subtitle' => 'The Project service allows you to manage all the projects in your Appwrite server.',
'description' => '',
'controller' => 'api/project.php',
'sdk' => true,
'docs' => true,
'docsUrl' => '',
'tests' => false,
'optional' => false,
'icon' => '',
],
'storage' => [
'key' => 'storage',
'name' => 'Storage',

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -306,24 +306,33 @@ return [
'question' => '',
'filter' => 'password'
],
// [
// 'name' => '_APP_CONNECTIONS_DB_PROJECT',
// 'description' => 'A list of comma-separated key value pairs representing Project DBs where key is the database name and value is the DSN connection string.',
// 'introduction' => 'TBD',
// 'default' => 'db_fra1_01=mysql://user:password@mariadb:3306/appwrite',
// 'required' => true,
// 'question' => '',
// 'filter' => ''
// ],
// [
// 'name' => '_APP_CONNECTIONS_DB_CONSOLE',
// 'description' => 'A key value pair representing the Console DB where key is the database name and value is the DSN connection string.',
// 'introduction' => 'TBD',
// 'default' => 'db_fra1_01=mysql://user:password@mariadb:3306/appwrite',
// 'required' => true,
// 'question' => '',
// 'filter' => ''
// ]
[
'name' => '_APP_DB_MAX_CONNECTIONS',
'description' => 'MariaDB server maximum connections.',
'introduction' => 'TBD',
'default' => 251,
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_CONNECTIONS_DB_PROJECT',
'description' => 'A list of comma-separated key value pairs representing Project DBs where key is the database name and value is the DSN connection string.',
'introduction' => 'TBD',
'default' => 'db_fra1_01=mysql://user:password@mariadb:3306/appwrite',
'required' => true,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_CONNECTIONS_DB_CONSOLE',
'description' => 'A key value pair representing the Console DB where key is the database name and value is the DSN connection string.',
'introduction' => 'TBD',
'default' => 'db_fra1_01=mysql://user:password@mariadb:3306/appwrite',
'required' => true,
'question' => '',
'filter' => ''
]
],
],
[
@ -701,7 +710,7 @@ return [
],
[
'name' => '_APP_FUNCTIONS_CONTAINERS',
'description' => 'The maximum number of containers Appwrite is allowed to keep alive in the background for function environments. Running containers allow faster execution time as there is no need to recreate each container every time a function gets executed. The default value is 10.',
'description' => 'Deprecated since 1.2.0. Runtimes now timeout by inactivity using \'_APP_FUNCTIONS_INACTIVE_THRESHOLD\'.',
'introduction' => '0.7.0',
'default' => '10',
'required' => false,
@ -728,7 +737,7 @@ return [
],
[
'name' => '_APP_FUNCTIONS_MEMORY_SWAP',
'description' => 'The maximum amount of swap memory a single cloud function is allowed to use in megabytes. The default value is empty. When it\'s empty, swap memory limit will be disabled.',
'description' => 'Deprecated since 1.2.0. High use of swap memory is not recommended to preserve harddrive health.',
'introduction' => '0.7.0',
'default' => '0',
'required' => false,
@ -782,7 +791,7 @@ return [
],
[
'name' => '_APP_FUNCTIONS_INACTIVE_THRESHOLD',
'description' => 'The minimum time a function can be inactive before it\'s container is shutdown and put to sleep. The default value is 60 seconds',
'description' => 'The minimum time a function can be inactive before it\'s container is shutdown and put to sleep. The default value is 60 seconds.',
'introduction' => '0.13.0',
'default' => '60',
'required' => false,
@ -791,7 +800,7 @@ return [
],
[
'name' => 'DOCKERHUB_PULL_USERNAME',
'description' => 'The username for hub.docker.com. This variable is used to pull images from hub.docker.com.',
'description' => 'Deprecated with 1.2.0, use \'_APP_DOCKER_HUB_USERNAME\' instead!',
'introduction' => '0.10.0',
'default' => '',
'required' => false,
@ -800,7 +809,7 @@ return [
],
[
'name' => 'DOCKERHUB_PULL_PASSWORD',
'description' => 'The password for hub.docker.com. This variable is used to pull images from hub.docker.com.',
'description' => 'Deprecated with 1.2.0, use \'_APP_DOCKER_HUB_PASSWORD\' instead!',
'introduction' => '0.10.0',
'default' => '',
'required' => false,
@ -809,7 +818,7 @@ return [
],
[
'name' => 'DOCKERHUB_PULL_EMAIL',
'description' => 'The email for hub.docker.com. This variable is used to pull images from hub.docker.com.',
'description' => 'Deprecated since 1.2.0. Email is no longer needed.',
'introduction' => '0.10.0',
'default' => '',
'required' => false,
@ -818,13 +827,49 @@ return [
],
[
'name' => 'OPEN_RUNTIMES_NETWORK',
'description' => 'The docker network used for communication between the executor and runtimes. Change this if you have altered the default network names.',
'description' => 'Deprecated with 1.2.0, use \'_APP_FUNCTIONS_RUNTIMES_NETWORK\' instead!',
'introduction' => '0.13.0',
'default' => 'appwrite_runtimes',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_FUNCTIONS_RUNTIMES_NETWORK',
'description' => 'The docker network used for communication between the executor and runtimes. Change this if you have altered the default network names.',
'introduction' => '1.2.0',
'default' => 'openruntimes-runtimes',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_DOCKER_HUB_USERNAME',
'description' => 'The username for hub.docker.com. This variable is used to pull images from hub.docker.com.',
'introduction' => '1.2.0',
'default' => '',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_DOCKER_HUB_PASSWORD',
'description' => 'The password for hub.docker.com. This variable is used to pull images from hub.docker.com.',
'introduction' => '1.2.0',
'default' => '',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_FUNCTIONS_CONNECTION_STORAGE',
'description' => 'DSN record of storage driver where Open Runtimes Executor stores output of Appwrite Function Deployment builds.',
'introduction' => '1.2.0',
'default' => '',
'required' => false,
'question' => '',
'filter' => ''
]
],
],
[

View file

@ -1196,13 +1196,12 @@ App::post('/v1/functions/:functionId/executions')
$executionResponse = $executor->createExecution(
projectId: $project->getId(),
deploymentId: $deployment->getId(),
path: $build->getAttribute('outputPath', ''),
vars: $vars,
data: $data,
entrypoint: $deployment->getAttribute('entrypoint', ''),
runtime: $function->getAttribute('runtime', ''),
payload: $data,
variables: $vars,
timeout: $function->getAttribute('timeout', 0),
baseImage: $runtime['image']
image: $runtime['image'],
source: $build->getAttribute('outputPath', ''),
entrypoint: $deployment->getAttribute('entrypoint', ''),
);
/** Update execution status */

View file

@ -0,0 +1,111 @@
<?php
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Validator\WhiteList;
App::get('/v1/project/usage')
->desc('Get usage stats for a project')
->groups(['api'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'project')
->label('sdk.method', 'getUsage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_PROJECT)
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForProject')
->action(function (string $range, Response $response, Database $dbForProject) {
$usage = [];
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$periods = [
'24h' => [
'period' => '30m',
'limit' => 48,
],
'7d' => [
'period' => '1d',
'limit' => 7,
],
'30d' => [
'period' => '1d',
'limit' => 30,
],
'90d' => [
'period' => '1d',
'limit' => 90,
],
];
$metrics = [
'project.$all.network.requests',
'project.$all.network.bandwidth',
'project.$all.storage.size',
'users.$all.count.total',
'collections.$all.count.total',
'documents.$all.count.total',
'executions.$all.compute.total',
];
$stats = [];
Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$limit = $periods[$range]['limit'];
$period = $periods[$range]['period'];
$requestDocs = $dbForProject->find('stats', [
Query::equal('period', [$period]),
Query::equal('metric', [$metric]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$stats[$metric] = [];
foreach ($requestDocs as $requestDoc) {
$stats[$metric][] = [
'value' => $requestDoc->getAttribute('value'),
'date' => $requestDoc->getAttribute('time'),
];
}
// backfill metrics with empty values for graphs
$backfill = $limit - \count($requestDocs);
while ($backfill > 0) {
$last = $limit - $backfill - 1; // array index of last added metric
$diff = match ($period) { // convert period to seconds for unix timestamp math
'30m' => 1800,
'1d' => 86400,
};
$stats[$metric][] = [
'value' => 0,
'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)),
];
$backfill--;
}
$stats[$metric] = array_reverse($stats[$metric]);
}
});
$usage = new Document([
'range' => $range,
'requests' => $stats[$metrics[0]] ?? [],
'network' => $stats[$metrics[1]] ?? [],
'storage' => $stats[$metrics[2]] ?? [],
'users' => $stats[$metrics[3]] ?? [],
'collections' => $stats[$metrics[4]] ?? [],
'documents' => $stats[$metrics[5]] ?? [],
'executions' => $stats[$metrics[6]] ?? [],
]);
}
$response->dynamic($usage, Response::MODEL_USAGE_PROJECT);
});

View file

@ -22,11 +22,9 @@ use Utopia\Database\DateTime;
use Utopia\Database\Permission;
use Utopia\Database\Query;
use Utopia\Database\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\DatetimeValidator;
use Utopia\Database\Validator\UID;
use Utopia\Domains\Domain;
use Utopia\Registry\Registry;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Database\Validator\Queries\Projects;
use Utopia\Cache\Cache;
@ -250,118 +248,6 @@ App::get('/v1/projects/:projectId')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::get('/v1/projects/:projectId/usage')
->desc('Get usage stats for a project')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getUsage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForConsole')
->inject('dbForProject')
->inject('register')
->action(function (string $projectId, string $range, Response $response, Database $dbForConsole, Database $dbForProject, Registry $register) {
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$usage = [];
if (App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
$periods = [
'24h' => [
'period' => '30m',
'limit' => 48,
],
'7d' => [
'period' => '1d',
'limit' => 7,
],
'30d' => [
'period' => '1d',
'limit' => 30,
],
'90d' => [
'period' => '1d',
'limit' => 90,
],
];
$dbForProject->setNamespace("_{$project->getInternalId()}");
$metrics = [
'project.$all.network.requests',
'project.$all.network.bandwidth',
'project.$all.storage.size',
'users.$all.count.total',
'collections.$all.count.total',
'documents.$all.count.total',
'executions.$all.compute.total',
];
$stats = [];
Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$stats) {
foreach ($metrics as $metric) {
$limit = $periods[$range]['limit'];
$period = $periods[$range]['period'];
$requestDocs = $dbForProject->find('stats', [
Query::equal('period', [$period]),
Query::equal('metric', [$metric]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$stats[$metric] = [];
foreach ($requestDocs as $requestDoc) {
$stats[$metric][] = [
'value' => $requestDoc->getAttribute('value'),
'date' => $requestDoc->getAttribute('time'),
];
}
// backfill metrics with empty values for graphs
$backfill = $limit - \count($requestDocs);
while ($backfill > 0) {
$last = $limit - $backfill - 1; // array index of last added metric
$diff = match ($period) { // convert period to seconds for unix timestamp math
'30m' => 1800,
'1d' => 86400,
};
$stats[$metric][] = [
'value' => 0,
'date' => DateTime::formatTz(DateTime::addSeconds(new \DateTime($stats[$metric][$last]['date'] ?? null), -1 * $diff)),
];
$backfill--;
}
$stats[$metric] = array_reverse($stats[$metric]);
}
});
$usage = new Document([
'range' => $range,
'requests' => $stats[$metrics[0]] ?? [],
'network' => $stats[$metrics[1]] ?? [],
'storage' => $stats[$metrics[2]] ?? [],
'users' => $stats[$metrics[3]] ?? [],
'collections' => $stats[$metrics[4]] ?? [],
'documents' => $stats[$metrics[5]] ?? [],
'executions' => $stats[$metrics[6]] ?? [],
]);
}
$response->dynamic($usage, Response::MODEL_USAGE_PROJECT);
});
App::patch('/v1/projects/:projectId')
->desc('Update Project')
->groups(['api', 'projects'])

View file

@ -1,802 +0,0 @@
<?php
require_once __DIR__ . '/../vendor/autoload.php';
use Appwrite\Runtimes\Runtimes;
use Swoole\ConnectionPool;
use Swoole\Http\Request as SwooleRequest;
use Swoole\Http\Response as SwooleResponse;
use Swoole\Http\Server;
use Swoole\Process;
use Swoole\Runtime;
use Swoole\Timer;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Database\DateTime;
use Utopia\Logger\Log;
use Utopia\Logger\Logger;
use Utopia\Orchestration\Adapter\DockerCLI;
use Utopia\Orchestration\Orchestration;
use Utopia\Storage\Device;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Device\Backblaze;
use Utopia\Storage\Device\DOSpaces;
use Utopia\Storage\Device\Linode;
use Utopia\Storage\Device\Wasabi;
use Utopia\Storage\Device\S3;
use Utopia\Storage\Storage;
use Utopia\Swoole\Request;
use Utopia\Swoole\Response;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Assoc;
use Utopia\Validator\Boolean;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL);
/** Constants */
const MAINTENANCE_INTERVAL = 3600; // 3600 seconds = 1 hour
/**
* Create a Swoole table to store runtime information
*/
$activeRuntimes = new Swoole\Table(1024);
$activeRuntimes->column('id', Swoole\Table::TYPE_STRING, 256);
$activeRuntimes->column('created', Swoole\Table::TYPE_INT, 8);
$activeRuntimes->column('updated', Swoole\Table::TYPE_INT, 8);
$activeRuntimes->column('name', Swoole\Table::TYPE_STRING, 128);
$activeRuntimes->column('status', Swoole\Table::TYPE_STRING, 128);
$activeRuntimes->column('key', Swoole\Table::TYPE_STRING, 256);
$activeRuntimes->create();
/**
* Create orchestration pool
*/
$orchestrationPool = new ConnectionPool(function () {
$dockerUser = App::getEnv('DOCKERHUB_PULL_USERNAME', null);
$dockerPass = App::getEnv('DOCKERHUB_PULL_PASSWORD', null);
$orchestration = new Orchestration(new DockerCLI($dockerUser, $dockerPass));
return $orchestration;
}, 10);
/**
* Create logger instance
*/
$providerName = App::getEnv('_APP_LOGGING_PROVIDER', '');
$providerConfig = App::getEnv('_APP_LOGGING_CONFIG', '');
$logger = null;
if (!empty($providerName) && !empty($providerConfig) && Logger::hasProvider($providerName)) {
$classname = '\\Utopia\\Logger\\Adapter\\' . \ucfirst($providerName);
$adapter = new $classname($providerConfig);
$logger = new Logger($adapter);
}
function logError(Throwable $error, string $action, Utopia\Route $route = null)
{
global $logger;
if ($logger) {
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
$log = new Log();
$log->setNamespace("executor");
$log->setServer(\gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
if ($route) {
$log->addTag('method', $route->getMethod());
$log->addTag('url', $route->getPath());
}
$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());
$log->addExtra('detailedTrace', $error->getTrace());
$log->setAction($action);
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
$responseCode = $logger->addLog($log);
Console::info('Executor log pushed with status code: ' . $responseCode);
}
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());
}
function getStorageDevice($root): Device
{
switch (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL)) {
case Storage::DEVICE_LOCAL:
default:
return new Local($root);
case Storage::DEVICE_S3:
$s3AccessKey = App::getEnv('_APP_STORAGE_S3_ACCESS_KEY', '');
$s3SecretKey = App::getEnv('_APP_STORAGE_S3_SECRET', '');
$s3Region = App::getEnv('_APP_STORAGE_S3_REGION', '');
$s3Bucket = App::getEnv('_APP_STORAGE_S3_BUCKET', '');
$s3Acl = 'private';
return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl);
case Storage::DEVICE_DO_SPACES:
$doSpacesAccessKey = App::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', '');
$doSpacesSecretKey = App::getEnv('_APP_STORAGE_DO_SPACES_SECRET', '');
$doSpacesRegion = App::getEnv('_APP_STORAGE_DO_SPACES_REGION', '');
$doSpacesBucket = App::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', '');
$doSpacesAcl = 'private';
return new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl);
case Storage::DEVICE_BACKBLAZE:
$backblazeAccessKey = App::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', '');
$backblazeSecretKey = App::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', '');
$backblazeRegion = App::getEnv('_APP_STORAGE_BACKBLAZE_REGION', '');
$backblazeBucket = App::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', '');
$backblazeAcl = 'private';
return new Backblaze($root, $backblazeAccessKey, $backblazeSecretKey, $backblazeBucket, $backblazeRegion, $backblazeAcl);
case Storage::DEVICE_LINODE:
$linodeAccessKey = App::getEnv('_APP_STORAGE_LINODE_ACCESS_KEY', '');
$linodeSecretKey = App::getEnv('_APP_STORAGE_LINODE_SECRET', '');
$linodeRegion = App::getEnv('_APP_STORAGE_LINODE_REGION', '');
$linodeBucket = App::getEnv('_APP_STORAGE_LINODE_BUCKET', '');
$linodeAcl = 'private';
return new Linode($root, $linodeAccessKey, $linodeSecretKey, $linodeBucket, $linodeRegion, $linodeAcl);
case Storage::DEVICE_WASABI:
$wasabiAccessKey = App::getEnv('_APP_STORAGE_WASABI_ACCESS_KEY', '');
$wasabiSecretKey = App::getEnv('_APP_STORAGE_WASABI_SECRET', '');
$wasabiRegion = App::getEnv('_APP_STORAGE_WASABI_REGION', '');
$wasabiBucket = App::getEnv('_APP_STORAGE_WASABI_BUCKET', '');
$wasabiAcl = 'private';
return new Wasabi($root, $wasabiAccessKey, $wasabiSecretKey, $wasabiBucket, $wasabiRegion, $wasabiAcl);
}
}
App::post('/v1/runtimes')
->desc("Create a new runtime server")
->param('runtimeId', '', new Text(64), 'Unique runtime ID.')
->param('source', '', new Text(0), 'Path to source files.')
->param('destination', '', new Text(0), 'Destination folder to store build files into.', true)
->param('vars', [], new Assoc(), 'Environment Variables required for the build.')
->param('commands', [], new ArrayList(new Text(1024), 100), 'Commands required to build the container. Maximum of 100 commands are allowed, each 1024 characters long.')
->param('runtime', '', new Text(128), 'Runtime for the cloud function.')
->param('baseImage', '', new Text(128), 'Base image name of the runtime.')
->param('entrypoint', '', new Text(256), 'Entrypoint of the code file.', true)
->param('remove', false, new Boolean(), 'Remove a runtime after execution.')
->param('workdir', '', new Text(256), 'Working directory.', true)
->inject('orchestrationPool')
->inject('activeRuntimes')
->inject('response')
->action(function (string $runtimeId, string $source, string $destination, array $vars, array $commands, string $runtime, string $baseImage, string $entrypoint, bool $remove, string $workdir, $orchestrationPool, $activeRuntimes, Response $response) {
if ($activeRuntimes->exists($runtimeId)) {
if ($activeRuntimes->get($runtimeId)['status'] == 'pending') {
throw new \Exception('A runtime with the same ID is already being created. Attempt a execution soon.', 500);
}
throw new Exception('Runtime already exists.', 409);
}
$container = [];
$containerId = '';
$stdout = '';
$stderr = '';
$startTime = DateTime::now();
$startTimeUnix = (new \DateTime($startTime))->getTimestamp();
$endTimeUnix = 0;
$orchestration = $orchestrationPool->get();
$secret = \bin2hex(\random_bytes(16));
if (!$remove) {
$activeRuntimes->set($runtimeId, [
'id' => $containerId,
'name' => $runtimeId,
'created' => $startTimeUnix,
'updated' => $endTimeUnix,
'status' => 'pending',
'key' => $secret,
]);
}
try {
Console::info('Building container : ' . $runtimeId);
/**
* Temporary file paths in the executor
*/
$tmpSource = "/tmp/$runtimeId/src/code.tar.gz";
$tmpBuild = "/tmp/$runtimeId/builds/code.tar.gz";
/**
* Copy code files from source to a temporary location on the executor
*/
$sourceDevice = getStorageDevice("/");
$localDevice = new Local();
$buffer = $sourceDevice->read($source);
if (!$localDevice->write($tmpSource, $buffer)) {
throw new Exception('Failed to copy source code to temporary directory', 500);
};
/**
* Create the mount folder
*/
if (!\file_exists(\dirname($tmpBuild))) {
if (!@\mkdir(\dirname($tmpBuild), 0755, true)) {
throw new Exception("Failed to create temporary directory", 500);
}
}
/**
* Create container
*/
$vars = \array_merge($vars, [
'INTERNAL_RUNTIME_KEY' => $secret,
'INTERNAL_RUNTIME_ENTRYPOINT' => $entrypoint,
]);
$vars = array_map(fn ($v) => strval($v), $vars);
$orchestration
->setCpus((int) App::getEnv('_APP_FUNCTIONS_CPUS', 0))
->setMemory((int) App::getEnv('_APP_FUNCTIONS_MEMORY', 0))
->setSwap((int) App::getEnv('_APP_FUNCTIONS_MEMORY_SWAP', 0));
/** Keep the container alive if we have commands to be executed */
$entrypoint = !empty($commands) ? [
'tail',
'-f',
'/dev/null'
] : [];
$containerId = $orchestration->run(
image: $baseImage,
name: $runtimeId,
hostname: $runtimeId,
vars: $vars,
command: $entrypoint,
labels: [
'openruntimes-id' => $runtimeId,
'openruntimes-type' => 'runtime',
'openruntimes-created' => strval($startTimeUnix),
'openruntimes-runtime' => $runtime,
],
workdir: $workdir,
volumes: [
\dirname($tmpSource) . ':/tmp:rw',
\dirname($tmpBuild) . ':/usr/code:rw'
]
);
if (empty($containerId)) {
throw new Exception('Failed to create build container', 500);
}
$orchestration->networkConnect($runtimeId, App::getEnv('OPEN_RUNTIMES_NETWORK', 'appwrite_runtimes'));
/**
* Execute any commands if they were provided
*/
if (!empty($commands)) {
$status = $orchestration->execute(
name: $runtimeId,
command: $commands,
stdout: $stdout,
stderr: $stderr,
timeout: App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900)
);
if (!$status) {
throw new Exception('Failed to build dependenices ' . $stderr, 500);
}
}
/**
* Move built code to expected build directory
*/
if (!empty($destination)) {
// Check if the build was successful by checking if file exists
if (!\file_exists($tmpBuild)) {
throw new Exception('Something went wrong during the build process', 500);
}
$destinationDevice = getStorageDevice($destination);
$outputPath = $destinationDevice->getPath(\uniqid() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
$buffer = $localDevice->read($tmpBuild);
if (!$destinationDevice->write($outputPath, $buffer, $localDevice->getFileMimeType($tmpBuild))) {
throw new Exception('Failed to move built code to storage', 500);
};
$container['outputPath'] = $outputPath;
}
if (empty($stdout)) {
$stdout = 'Build Successful!';
}
$endTime = DateTime::now();
$endTimeUnix = (new \DateTime($endTime))->getTimestamp();
$duration = $endTimeUnix - $startTimeUnix;
$container = array_merge($container, [
'status' => 'ready',
'response' => \mb_strcut($stdout, 0, 1000000), // Limit to 1MB
'stderr' => \mb_strcut($stderr, 0, 1000000), // Limit to 1MB
'startTime' => $startTime,
'endTime' => $endTime,
'duration' => $duration,
]);
if (!$remove) {
$activeRuntimes->set($runtimeId, [
'id' => $containerId,
'name' => $runtimeId,
'created' => $startTimeUnix,
'updated' => $endTimeUnix,
'status' => 'Up ' . \round($duration, 2) . 's',
'key' => $secret,
]);
}
Console::success('Build Stage completed in ' . ($duration) . ' seconds');
} catch (Throwable $th) {
Console::error('Build failed: ' . $th->getMessage() . $stdout);
throw new Exception($th->getMessage() . $stdout, 500);
} finally {
// Container cleanup
if ($remove) {
if (!empty($containerId)) {
// If container properly created
$orchestration->remove($containerId, true);
$activeRuntimes->del($runtimeId);
} else {
// If whole creation failed, but container might have been initialized
try {
// Try to remove with contaier name instead of ID
$orchestration->remove($runtimeId, true);
$activeRuntimes->del($runtimeId);
} catch (Throwable $th) {
// If fails, means initialization also failed.
// Contianer is not there, no need to remove
}
}
}
// Release orchestration back to pool, we are done with it
$orchestrationPool->put($orchestration);
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($container);
});
App::get('/v1/runtimes')
->desc("List currently active runtimes")
->inject('activeRuntimes')
->inject('response')
->action(function ($activeRuntimes, Response $response) {
$runtimes = [];
foreach ($activeRuntimes as $runtime) {
$runtimes[] = $runtime;
}
$response
->setStatusCode(Response::STATUS_CODE_OK)
->json($runtimes);
});
App::get('/v1/runtimes/:runtimeId')
->desc("Get a runtime by its ID")
->param('runtimeId', '', new Text(64), 'Runtime unique ID.')
->inject('activeRuntimes')
->inject('response')
->action(function ($runtimeId, $activeRuntimes, Response $response) {
if (!$activeRuntimes->exists($runtimeId)) {
throw new Exception('Runtime not found', 404);
}
$runtime = $activeRuntimes->get($runtimeId);
$response
->setStatusCode(Response::STATUS_CODE_OK)
->json($runtime);
});
App::delete('/v1/runtimes/:runtimeId')
->desc('Delete a runtime')
->param('runtimeId', '', new Text(64), 'Runtime unique ID.', false)
->inject('orchestrationPool')
->inject('activeRuntimes')
->inject('response')
->action(function (string $runtimeId, $orchestrationPool, $activeRuntimes, Response $response) {
if (!$activeRuntimes->exists($runtimeId)) {
throw new Exception('Runtime not found', 404);
}
Console::info('Deleting runtime: ' . $runtimeId);
try {
$orchestration = $orchestrationPool->get();
$orchestration->remove($runtimeId, true);
$activeRuntimes->del($runtimeId);
Console::success('Removed runtime container: ' . $runtimeId);
} finally {
$orchestrationPool->put($orchestration);
}
// Remove all the build containers with that same ID
// TODO:: Delete build containers
// foreach ($buildIds as $buildId) {
// try {
// Console::info('Deleting build container : ' . $buildId);
// $status = $orchestration->remove('build-' . $buildId, true);
// } catch (Throwable $th) {
// Console::error($th->getMessage());
// }
// }
$response
->setStatusCode(Response::STATUS_CODE_OK)
->send();
});
App::post('/v1/execution')
->desc('Create an execution')
->param('runtimeId', '', new Text(64), 'The runtimeID to execute.')
->param('vars', [], new Assoc(), 'Environment variables required for the build.')
->param('data', '', new Text(8192), 'Data to be forwarded to the function, this is user specified.', true)
->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Function maximum execution time in seconds.')
->inject('activeRuntimes')
->inject('response')
->action(
function (string $runtimeId, array $vars, string $data, $timeout, $activeRuntimes, Response $response) {
if (!$activeRuntimes->exists($runtimeId)) {
throw new Exception('Runtime not found. Please create the runtime.', 404);
}
for ($i = 0; $i < 5; $i++) {
if ($activeRuntimes->get($runtimeId)['status'] === 'pending') {
Console::info('Waiting for runtime to be ready...');
sleep(1);
} else {
break;
}
if ($i === 4) {
throw new Exception('Runtime failed to launch in allocated time.', 500);
}
}
$runtime = $activeRuntimes->get($runtimeId);
$secret = $runtime['key'];
if (empty($secret)) {
throw new Exception('Runtime secret not found. Please re-create the runtime.', 500);
}
Console::info('Executing Runtime: ' . $runtimeId);
$execution = [];
$executionStart = \microtime(true);
$stdout = '';
$stderr = '';
$res = '';
$statusCode = 0;
$errNo = -1;
$executorResponse = '';
$timeout ??= (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900);
$ch = \curl_init();
$body = \json_encode([
'variables' => $vars,
'payload' => $data,
'timeout' => $timeout
]);
\curl_setopt($ch, CURLOPT_URL, "http://" . $runtimeId . ":3000/");
\curl_setopt($ch, CURLOPT_POST, true);
\curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
\curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
\curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
\curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Content-Length: ' . \strlen($body),
'x-internal-challenge: ' . $secret,
'host: null'
]);
$executorResponse = \curl_exec($ch);
$executorResponse = json_decode($executorResponse, true);
$statusCode = \curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = \curl_error($ch);
$errNo = \curl_errno($ch);
\curl_close($ch);
switch (true) {
/** No Error. */
case $errNo === 0:
break;
/** Runtime not ready for requests yet. 111 is the swoole error code for Connection Refused - see https://openswoole.com/docs/swoole-error-code */
case $errNo === 111:
throw new Exception('An internal curl error has occurred within the executor! Error Msg: ' . $error, 406);
/** Any other CURL error */
default:
throw new Exception('An internal curl error has occurred within the executor! Error Msg: ' . $error, 500);
}
switch (true) {
case $statusCode >= 500:
$stderr = ($executorResponse ?? [])['stderr'] ?? 'Internal Runtime error.';
$stdout = ($executorResponse ?? [])['stdout'] ?? 'Internal Runtime error.';
break;
case $statusCode >= 100:
$stdout = $executorResponse['stdout'];
$res = $executorResponse['response'];
if (is_array($res)) {
$res = json_encode($res, JSON_UNESCAPED_UNICODE);
}
break;
default:
$stderr = ($executorResponse ?? [])['stderr'] ?? 'Execution failed.';
$stdout = ($executorResponse ?? [])['stdout'] ?? '';
break;
}
$executionEnd = \microtime(true);
$executionTime = ($executionEnd - $executionStart);
$functionStatus = ($statusCode >= 500) ? 'failed' : 'completed';
Console::success('Function executed in ' . $executionTime . ' seconds, status: ' . $functionStatus);
$execution = [
'status' => $functionStatus,
'statusCode' => $statusCode,
'response' => \mb_strcut($res, 0, 1000000), // Limit to 1MB
'stdout' => \mb_strcut($stdout, 0, 1000000), // Limit to 1MB
'stderr' => \mb_strcut($stderr, 0, 1000000), // Limit to 1MB
'duration' => $executionTime,
];
/** Update swoole table */
$runtime['updated'] = \time();
$activeRuntimes->set($runtimeId, $runtime);
$response
->setStatusCode(Response::STATUS_CODE_OK)
->json($execution);
}
);
App::setMode(App::MODE_TYPE_PRODUCTION); // Define Mode
$http = new Server("0.0.0.0", 80);
/** Set Resources */
App::setResource('orchestrationPool', fn() => $orchestrationPool);
App::setResource('activeRuntimes', fn() => $activeRuntimes);
/** Set callbacks */
App::error()
->inject('utopia')
->inject('error')
->inject('request')
->inject('response')
->action(function (App $utopia, throwable $error, Request $request, Response $response) {
$route = $utopia->match($request);
logError($error, "httpError", $route);
switch ($error->getCode()) {
case 400: // Error allowed publicly
case 401: // Error allowed publicly
case 402: // Error allowed publicly
case 403: // Error allowed publicly
case 404: // Error allowed publicly
case 406: // Error allowed publicly
case 409: // Error allowed publicly
case 412: // Error allowed publicly
case 425: // Error allowed publicly
case 429: // Error allowed publicly
case 501: // Error allowed publicly
case 503: // Error allowed publicly
$code = $error->getCode();
break;
default:
$code = 500; // All other errors get the generic 500 server error status code
}
$output = [
'message' => $error->getMessage(),
'code' => $error->getCode(),
'file' => $error->getFile(),
'line' => $error->getLine(),
'trace' => $error->getTrace(),
'version' => App::getEnv('_APP_VERSION', 'UNKNOWN')
];
$response
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
->addHeader('Expires', '0')
->addHeader('Pragma', 'no-cache')
->setStatusCode($code);
$response->json($output);
});
App::init()
->inject('request')
->action(function (Request $request) {
$secretKey = $request->getHeader('x-appwrite-executor-key', '');
if (empty($secretKey)) {
throw new Exception('Missing executor key', 401);
}
if ($secretKey !== App::getEnv('_APP_EXECUTOR_SECRET', '')) {
throw new Exception('Missing executor key', 401);
}
});
$http->on('start', function ($http) {
global $orchestrationPool;
global $activeRuntimes;
/**
* Warmup: make sure images are ready to run fast 🚀
*/
$runtimes = new Runtimes('v2');
$allowList = empty(App::getEnv('_APP_FUNCTIONS_RUNTIMES')) ? [] : \explode(',', App::getEnv('_APP_FUNCTIONS_RUNTIMES'));
$runtimes = $runtimes->getAll(true, $allowList);
foreach ($runtimes as $runtime) {
go(function () use ($runtime, $orchestrationPool) {
try {
$orchestration = $orchestrationPool->get();
Console::info('Warming up ' . $runtime['name'] . ' ' . $runtime['version'] . ' environment...');
$response = $orchestration->pull($runtime['image']);
if ($response) {
Console::success("Successfully Warmed up {$runtime['name']} {$runtime['version']}!");
} else {
Console::warning("Failed to Warmup {$runtime['name']} {$runtime['version']}!");
}
} catch (\Throwable $th) {
} finally {
$orchestrationPool->put($orchestration);
}
});
}
/**
* Remove residual runtimes
*/
Console::info('Removing orphan runtimes...');
try {
$orchestration = $orchestrationPool->get();
$orphans = $orchestration->list(['label' => 'openruntimes-type=runtime']);
} finally {
$orchestrationPool->put($orchestration);
}
foreach ($orphans as $runtime) {
go(function () use ($runtime, $orchestrationPool) {
try {
$orchestration = $orchestrationPool->get();
$orchestration->remove($runtime->getName(), true);
Console::success("Successfully removed {$runtime->getName()}");
} catch (\Throwable $th) {
Console::error('Orphan runtime deletion failed: ' . $th->getMessage());
} finally {
$orchestrationPool->put($orchestration);
}
});
}
/**
* Register handlers for shutdown
*/
@Process::signal(SIGINT, function () use ($http) {
$http->shutdown();
});
@Process::signal(SIGQUIT, function () use ($http) {
$http->shutdown();
});
@Process::signal(SIGKILL, function () use ($http) {
$http->shutdown();
});
@Process::signal(SIGTERM, function () use ($http) {
$http->shutdown();
});
/**
* Run a maintenance worker every MAINTENANCE_INTERVAL seconds to remove inactive runtimes
*/
Timer::tick(MAINTENANCE_INTERVAL * 1000, function () use ($orchestrationPool, $activeRuntimes) {
Console::warning("Running maintenance task ...");
foreach ($activeRuntimes as $runtime) {
$inactiveThreshold = \time() - App::getEnv('_APP_FUNCTIONS_INACTIVE_THRESHOLD', 60);
if ($runtime['updated'] < $inactiveThreshold) {
go(function () use ($runtime, $orchestrationPool, $activeRuntimes) {
try {
$orchestration = $orchestrationPool->get();
$orchestration->remove($runtime['name'], true);
$activeRuntimes->del($runtime['name']);
Console::success("Successfully removed {$runtime['name']}");
} catch (\Throwable $th) {
Console::error('Inactive Runtime deletion failed: ' . $th->getMessage());
} finally {
$orchestrationPool->put($orchestration);
}
});
}
}
});
});
$http->on('beforeShutdown', function () {
global $orchestrationPool;
Console::info('Cleaning up containers before shutdown...');
$orchestration = $orchestrationPool->get();
$functionsToRemove = $orchestration->list(['label' => 'openruntimes-type=runtime']);
$orchestrationPool->put($orchestration);
foreach ($functionsToRemove as $container) {
go(function () use ($orchestrationPool, $container) {
try {
$orchestration = $orchestrationPool->get();
$orchestration->remove($container->getId(), true);
Console::info('Removed container ' . $container->getName());
} catch (\Throwable $th) {
Console::error('Failed to remove container: ' . $container->getName());
} finally {
$orchestrationPool->put($orchestration);
}
});
}
});
$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) {
$request = new Request($swooleRequest);
$response = new Response($swooleResponse);
$app = new App('UTC');
try {
$app->run($request, $response);
} catch (\Throwable $th) {
logError($th, "serverError");
$swooleResponse->setStatusCode(500);
$output = [
'message' => 'Error: ' . $th->getMessage(),
'code' => 500,
'file' => $th->getFile(),
'line' => $th->getLine(),
'trace' => $th->getTrace()
];
$swooleResponse->end(\json_encode($output));
}
});
$http->start();

View file

@ -97,6 +97,7 @@ const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
const APP_CACHE_BUSTER = 501;
const APP_VERSION_STABLE = '1.0.3';
const APP_DEFAULT_POOL_SIZE = 64;
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
@ -496,7 +497,7 @@ $register->set('logger', function () {
$adapter = new $classname($providerConfig);
return new Logger($adapter);
});
$register->set('pools', function () {
$register->set('pools', function ($size = APP_DEFAULT_POOL_SIZE) {
$group = new Group();
$fallbackForDB = AppwriteURL::unparse([
@ -620,7 +621,7 @@ $register->set('pools', function () {
break;
}
$pool = new Pool($name, 64, function () use ($type, $resource, $dsn) {
$pool = new Pool($name, $size, function () use ($type, $resource, $dsn) {
// Get Adapter
$adapter = null;
@ -1122,6 +1123,26 @@ function getDevice($root): Device
}
}
/**
* Get database connection pool size for worker processes.
*
* @return int
* @throws \Exception
*/
function getWorkerPoolSize(): int
{
$reservedConnections = APP_DEFAULT_POOL_SIZE; // Pool of default size is reserved for the HTTP API
$workerCount = swoole_cpu_num() * intval(App::getEnv('_APP_WORKER_PER_CORE', 6));
$maxConnections = App::getenv('_APP_DB_MAX_CONNECTIONS', 251);
$workerConnections = $maxConnections - $reservedConnections;
if ($workerCount > $workerConnections) {
throw new \Exception('Worker pool size is too small. Increase the number of allowed database connections or decrease the number of workers.', 500);
}
return (int)($workerConnections / $workerCount);
}
App::setResource('mode', function ($request) {
/** @var Appwrite\Utopia\Request $request */

View file

@ -36,7 +36,8 @@ function getConsoleDB(): Database
{
global $register;
$pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */
/** @var \Utopia\Pools\Group $pools */
$pools = $register->get('pools', args: [getWorkerPoolSize()]);
$dbAdapter = $pools
->get('console')
@ -55,7 +56,8 @@ function getProjectDB(Document $project): Database
{
global $register;
$pools = $register->get('pools'); /** @var \Utopia\Pools\Group $pools */
/** @var \Utopia\Pools\Group $pools */
$pools = $register->get('pools', args: [getWorkerPoolSize()]);
if ($project->isEmpty() || $project->getId() === 'console') {
return getConsoleDB();
@ -364,10 +366,11 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
call_user_func($logError, $th, "pubSubConnection");
Console::error('Pub/sub error: ' . $th->getMessage());
$register->get('pools')->reclaim();
$attempts++;
sleep(DATABASE_RECONNECT_SLEEP);
continue;
} finally {
$register->get('pools')->reclaim();
}
}
@ -556,7 +559,6 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
default:
throw new Exception('Message type is not valid.', 1003);
break;
}
} catch (\Throwable $th) {
$response = [

View file

@ -1,24 +0,0 @@
<?php
global $cli;
use Appwrite\Event\Certificate;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Database\Document;
use Utopia\Validator\Hostname;
$cli
->task('ssl')
->desc('Validate server certificates')
->param('domain', App::getEnv('_APP_DOMAIN', ''), new Hostname(), 'Domain to generate certificate for. If empty, main domain will be used.', true)
->action(function ($domain) {
Console::success('Scheduling a job to issue a TLS certificate for domain: ' . $domain);
(new Certificate())
->setDomain(new Document([
'domain' => $domain
]))
->setSkipRenewCheck(true)
->trigger();
});

View file

@ -1,111 +0,0 @@
<?php
global $cli, $register;
use Appwrite\Usage\Calculators\Aggregator;
use Appwrite\Usage\Calculators\Database;
use Appwrite\Usage\Calculators\TimeSeries;
use InfluxDB\Database as InfluxDatabase;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Database\Database as UtopiaDatabase;
use Utopia\Database\Validator\Authorization;
use Utopia\Logger\Log;
use Utopia\Validator\WhiteList;
Authorization::disable();
Authorization::setDefaultStatus(false);
$logError = function (Throwable $error, string $action = 'syncUsageStats') use ($register) {
$logger = $register->get('logger');
if ($logger) {
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
$log = new Log();
$log->setNamespace("usage");
$log->setServer(\gethostname());
$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());
$log->addExtra('detailedTrace', $error->getTrace());
$log->setAction($action);
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
$responseCode = $logger->addLog($log);
Console::info('Usage stats log pushed with status code: ' . $responseCode);
}
Console::warning("Failed: {$error->getMessage()}");
Console::warning($error->getTraceAsString());
};
function aggregateTimeseries(UtopiaDatabase $database, InfluxDatabase $influxDB, callable $logError): void
{
$interval = (int) App::getEnv('_APP_USAGE_TIMESERIES_INTERVAL', '30'); // 30 seconds (by default)
$usage = new TimeSeries($database, $influxDB, $logError);
Console::loop(function () use ($interval, $usage) {
$now = date('d-m-Y H:i:s', time());
Console::info("[{$now}] Aggregating Timeseries Usage data every {$interval} seconds");
$loopStart = microtime(true);
$usage->collect();
$loopTook = microtime(true) - $loopStart;
$now = date('d-m-Y H:i:s', time());
Console::info("[{$now}] Aggregation took {$loopTook} seconds");
}, $interval);
}
function aggregateDatabase(UtopiaDatabase $database, callable $logError): void
{
$interval = (int) App::getEnv('_APP_USAGE_DATABASE_INTERVAL', '900'); // 15 minutes (by default)
$usage = new Database($database, $logError);
$aggregrator = new Aggregator($database, $logError);
Console::loop(function () use ($interval, $usage, $aggregrator) {
$now = date('d-m-Y H:i:s', time());
Console::info("[{$now}] Aggregating database usage every {$interval} seconds.");
$loopStart = microtime(true);
$usage->collect();
$aggregrator->collect();
$loopTook = microtime(true) - $loopStart;
$now = date('d-m-Y H:i:s', time());
Console::info("[{$now}] Aggregation took {$loopTook} seconds");
}, $interval);
}
$cli
->task('usage')
->param('type', 'timeseries', new WhiteList(['timeseries', 'database']))
->desc('Schedules syncing data from influxdb to Appwrite console db')
->action(function (string $type) use ($logError) {
Console::title('Usage Aggregation V1');
Console::success(APP_NAME . ' usage aggregation process v1 has started');
$database = getConsoleDB();
$influxDB = getInfluxDB();
switch ($type) {
case 'timeseries':
aggregateTimeseries($database, $influxDB, $logError);
break;
case 'database':
aggregateDatabase($database, $logError);
break;
default:
Console::error("Unsupported usage aggregation type");
}
});

View file

@ -34,13 +34,11 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="Usage 24h"
data-service="projects.getUsage"
data-event="submit"
data-scope="console"
data-service="project.getUsage"
data-name="usage"
data-param-project-id="{{router.params.project}}"
data-param-range="24h"
data-scope="console">
data-scope="sdk">
<button class="tick">24h</button>
</form>
@ -51,12 +49,11 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="Usage 30d"
data-service="projects.getUsage"
data-service="project.getUsage"
data-event="submit"
data-scope="console"
data-name="usage"
data-param-project-id="{{router.params.project}}"
data-scope="console">
data-param-range="30d"
data-scope="sdk">
<button class="tick">30d</button>
</form>
@ -67,13 +64,11 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
data-analytics-event="click"
data-analytics-category="console"
data-analytics-label="Usage 90d"
data-service="projects.getUsage"
data-service="project.getUsage"
data-event="submit"
data-scope="console"
data-name="usage"
data-param-project-id="{{router.params.project}}"
data-param-range="90d"
data-scope="console">
data-scope="sdk">
<button class="tick">90d</button>
</form>
@ -82,12 +77,11 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
<?php endif;?>
</div>
<div
data-service="projects.getUsage"
data-service="project.getUsage"
data-event="load"
data-scope="console"
data-name="usage"
data-param-project-id="{{router.params.project}}"
data-param-range="30d">
data-param-range="30d"
data-scope="sdk">
<?php if (!$graph && $usageStatsEnabled): ?>
<div class="box dashboard">
<div class="row responsive">

View file

@ -15,7 +15,7 @@ $image = $this->getParam('image', '');
services:
traefik:
image: traefik:2.7
image: traefik:2.9
container_name: appwrite-traefik
<<: *x-logging
command:
@ -30,6 +30,15 @@ services:
ports:
- <?php echo $httpPort; ?>:80
- <?php echo $httpsPort; ?>:443
ulimits:
nofile:
soft: 655350
hard: 655350
sysctls:
- net.core.somaxconn=1024
- net.ipv4.tcp_rmem=1024 4096 16384
- net.ipv4.tcp_wmem=1024 4096 16384
- net.ipv4.ip_local_port_range=1025 65535
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- appwrite-config:/storage/config:ro
@ -66,7 +75,7 @@ services:
- appwrite-cache:/storage/cache:rw
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
- appwrite-functions:/storage/functions:rw
- openruntimes-functions:/storage/functions:rw
depends_on:
- mariadb
- redis
@ -93,6 +102,7 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_MAX_CONNECTIONS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@ -134,10 +144,8 @@ services:
- _APP_FUNCTIONS_SIZE_LIMIT
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CONTAINERS
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_MEMORY_SWAP
- _APP_FUNCTIONS_RUNTIMES
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
@ -189,6 +197,7 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_MAX_CONNECTIONS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@ -259,8 +268,8 @@ services:
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw
- appwrite-functions:/storage/functions:rw
- appwrite-builds:/storage/builds:rw
- openruntimes-functions:/storage/functions:rw
- openruntimes-builds:/storage/builds:rw
- appwrite-certificates:/storage/certificates:rw
environment:
- _APP_ENV
@ -397,7 +406,7 @@ services:
depends_on:
- redis
- mariadb
- appwrite-executor
- openruntimes-executor
environment:
- _APP_ENV
- _APP_OPENSSL_KEY_V1
@ -414,66 +423,6 @@ services:
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
- _APP_USAGE_STATS
- DOCKERHUB_PULL_USERNAME
- DOCKERHUB_PULL_PASSWORD
appwrite-executor:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: executor
<<: *x-logging
container_name: appwrite-executor
restart: unless-stopped
stop_signal: SIGINT
networks:
appwrite:
runtimes:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- appwrite-functions:/storage/functions:rw
- appwrite-builds:/storage/builds:rw
- /tmp:/tmp:rw
depends_on:
- redis
- mariadb
- appwrite
environment:
- _APP_ENV
- _APP_VERSION
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CONTAINERS
- _APP_FUNCTIONS_RUNTIMES
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_MEMORY_SWAP
- _APP_FUNCTIONS_INACTIVE_THRESHOLD
- _APP_EXECUTOR_SECRET
- OPEN_RUNTIMES_NETWORK
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
- _APP_STORAGE_S3_REGION
- _APP_STORAGE_S3_BUCKET
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
- _APP_STORAGE_DO_SPACES_SECRET
- _APP_STORAGE_DO_SPACES_REGION
- _APP_STORAGE_DO_SPACES_BUCKET
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY
- _APP_STORAGE_BACKBLAZE_SECRET
- _APP_STORAGE_BACKBLAZE_REGION
- _APP_STORAGE_BACKBLAZE_BUCKET
- _APP_STORAGE_LINODE_ACCESS_KEY
- _APP_STORAGE_LINODE_SECRET
- _APP_STORAGE_LINODE_REGION
- _APP_STORAGE_LINODE_BUCKET
- _APP_STORAGE_WASABI_ACCESS_KEY
- _APP_STORAGE_WASABI_SECRET
- _APP_STORAGE_WASABI_REGION
- _APP_STORAGE_WASABI_BUCKET
- DOCKERHUB_PULL_USERNAME
- DOCKERHUB_PULL_PASSWORD
appwrite-worker-mails:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
@ -633,6 +582,32 @@ services:
- _APP_REDIS_USER
- _APP_REDIS_PASS
openruntimes-executor:
container_name: openruntimes-executor
hostname: exc1
<<: *x-logging
stop_signal: SIGINT
image: openruntimes/executor:0.1.4
networks:
- appwrite
- openruntimes-runtimes
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- openruntimes-builds:/storage/builds:rw
- openruntimes-functions:/storage/functions:rw
- /tmp:/tmp:rw
environment:
- OPR_EXECUTOR_CONNECTION_STORAGE=$_APP_FUNCTIONS_CONNECTION_STORAGE
- OPR_EXECUTOR_INACTIVE_TRESHOLD=$_APP_FUNCTIONS_INACTIVE_THRESHOLD
- OPR_EXECUTOR_NETWORK=$_APP_FUNCTIONS_RUNTIMES_NETWORK
- OPR_EXECUTOR_DOCKER_HUB_USERNAME=$_APP_DOCKER_HUB_USERNAME
- OPR_EXECUTOR_DOCKER_HUB_PASSWORD=$_APP_DOCKER_HUB_PASSWORD
- OPR_EXECUTOR_ENV=$_APP_ENV
- OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES
- OPR_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
- OPR_EXECUTOR_LOGGING_PROVIDER=$_APP_LOGGING_PROVIDER
- OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
mariadb:
image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
@ -647,7 +622,7 @@ services:
- MYSQL_DATABASE=${_APP_DB_SCHEMA}
- MYSQL_USER=${_APP_DB_USER}
- MYSQL_PASSWORD=${_APP_DB_PASS}
command: 'mysqld --innodb-flush-method=fsync'
command: 'mysqld --innodb-flush-method=fsync --max_connections=${_APP_DB_MAX_CONNECTIONS}'
redis:
image: redis:7.0.4-alpine
@ -696,8 +671,13 @@ services:
networks:
gateway:
name: gateway
appwrite:
runtimes:
name: appwrite
openruntimes-runtimes:
name: openruntimes-runtimes
openruntimes-executors:
name: openruntimes-executors
volumes:
appwrite-mariadb:
@ -705,8 +685,7 @@ volumes:
appwrite-cache:
appwrite-uploads:
appwrite-certificates:
appwrite-functions:
appwrite-builds:
appwrite-influxdb:
appwrite-config:
appwrite-executor:
openruntimes-functions:
openruntimes-builds:

View file

@ -150,20 +150,17 @@ class BuildsV1 extends Worker
return $carry;
}, []);
$baseImage = $runtime['image'];
try {
$response = $this->executor->createRuntime(
projectId: $project->getId(),
deploymentId: $deployment->getId(),
entrypoint: $deployment->getAttribute('entrypoint'),
source: $source,
destination: APP_STORAGE_BUILDS . "/app-{$project->getId()}",
vars: $vars,
runtime: $key,
baseImage: $baseImage,
workdir: '/usr/code',
image: $runtime['image'],
remove: true,
entrypoint: $deployment->getAttribute('entrypoint'),
workdir: '/usr/code',
destination: APP_STORAGE_BUILDS . "/app-{$project->getId()}",
variables: $vars,
commands: [
'sh', '-c',
'tar -zxf /tmp/code.tar.gz -C /usr/code && \
@ -171,13 +168,16 @@ class BuildsV1 extends Worker
]
);
$endTime = new \DateTime();
$endTime->setTimestamp($response['endTimeUnix']);
/** Update the build document */
$build->setAttribute('endTime', $response['endTime']);
$build->setAttribute('duration', $response['duration']);
$build->setAttribute('endTime', DateTime::format($endTime));
$build->setAttribute('duration', \intval($response['duration']));
$build->setAttribute('status', $response['status']);
$build->setAttribute('outputPath', $response['outputPath']);
$build->setAttribute('stderr', $response['stderr']);
$build->setAttribute('stdout', $response['response']);
$build->setAttribute('stdout', $response['stdout']);
Console::success("Build id: $buildId created");

View file

@ -470,16 +470,17 @@ class DeletesV1 extends Worker
/**
* Request executor to delete all deployment containers
* TODO: Re-enable. Disabled for now because of proxy. Container killed after inactivity automatically.
*/
Console::info("Requesting executor to delete all deployment containers for function " . $functionId);
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
foreach ($deploymentIds as $deploymentId) {
try {
$executor->deleteRuntime($projectId, $deploymentId);
} catch (Throwable $th) {
Console::error($th->getMessage());
}
}
// Console::info("Requesting executor to delete all deployment containers for function " . $functionId);
// $executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
// foreach ($deploymentIds as $deploymentId) {
// try {
// $executor->deleteRuntime($projectId, $deploymentId);
// } catch (Throwable $th) {
// Console::error($th->getMessage());
// }
// }
}
/**
@ -520,15 +521,16 @@ class DeletesV1 extends Worker
});
/**
* Request executor to delete the deployment container
* Request executor to delete the deployment container.
* TODO: Re-enable. Disabled for now because of proxy. Container killed after inactivity automatically.
*/
Console::info("Requesting executor to delete deployment container for deployment " . $deploymentId);
try {
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
$executor->deleteRuntime($projectId, $deploymentId);
} catch (Throwable $th) {
Console::error($th->getMessage());
}
// Console::info("Requesting executor to delete deployment container for deployment " . $deploymentId);
// try {
// $executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
// $executor->deleteRuntime($projectId, $deploymentId);
// } catch (Throwable $th) {
// Console::error($th->getMessage());
// }
}

View file

@ -289,13 +289,12 @@ class FunctionsV1 extends Worker
$executionResponse = $this->executor->createExecution(
projectId: $project->getId(),
deploymentId: $deploymentId,
path: $build->getAttribute('outputPath', ''),
vars: $vars,
entrypoint: $deployment->getAttribute('entrypoint', ''),
data: $vars['APPWRITE_FUNCTION_DATA'] ?? '',
runtime: $function->getAttribute('runtime', ''),
payload: $vars['APPWRITE_FUNCTION_DATA'] ?? '',
variables: $vars,
timeout: $function->getAttribute('timeout', 0),
baseImage: $runtime['image']
image: $runtime['image'],
source: $build->getAttribute('outputPath', ''),
entrypoint: $deployment->getAttribute('entrypoint', ''),
);
/** Update execution status */

View file

@ -1,3 +0,0 @@
#!/bin/sh
php -e /usr/src/code/app/executor.php -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php

View file

@ -43,25 +43,26 @@
"ext-sockets": "*",
"appwrite/php-clamav": "1.1.*",
"appwrite/php-runtimes": "0.11.*",
"utopia-php/framework": "0.23.*",
"utopia-php/logger": "0.3.*",
"utopia-php/abuse": "0.16.*",
"utopia-php/analytics": "0.2.*",
"utopia-php/cache": "0.8.*",
"utopia-php/audit": "0.17.*",
"utopia-php/cli": "0.13.*",
"utopia-php/cache": "0.8.*",
"utopia-php/cli": "0.14.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "0.28.*",
"utopia-php/locale": "0.4.*",
"utopia-php/registry": "0.5.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/domains": "1.1.*",
"utopia-php/swoole": "0.5.*",
"utopia-php/storage": "0.11.*",
"utopia-php/websocket": "0.1.0",
"utopia-php/framework": "0.25.*",
"utopia-php/image": "0.5.*",
"utopia-php/orchestration": "0.6.*",
"utopia-php/pools": "0.4.0",
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.3.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.3.*",
"utopia-php/pools": "0.4.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/registry": "dev-feat-allow-params as 0.5.0",
"utopia-php/storage": "0.11.*",
"utopia-php/swoole": "0.5.*",
"utopia-php/websocket": "0.1.0",
"resque/php-resque": "1.3.6",
"matomo/device-detector": "6.0.0",
"dragonmantank/cron-expression": "3.3.1",

282
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "51f81d435f4b5b7a9a6ea8f81b470353",
"content-hash": "b125039c64ae4cbe0d2a1b57322d0ebe",
"packages": [
{
"name": "adhocore/jwt",
@ -115,15 +115,15 @@
},
{
"name": "appwrite/php-runtimes",
"version": "0.11.0",
"version": "0.11.1",
"source": {
"type": "git",
"url": "https://github.com/appwrite/runtimes.git",
"reference": "547fc026e11c0946846a8ac690898f5bf53be101"
"reference": "9d74a477ba3333cbcfac565c46fcf19606b7b603"
},
"require": {
"php": ">=8.0",
"utopia-php/system": "0.4.*"
"utopia-php/system": "0.6.*"
},
"require-dev": {
"phpunit/phpunit": "^9.3",
@ -154,7 +154,7 @@
"php",
"runtimes"
],
"time": "2022-08-15T14:03:36+00:00"
"time": "2022-11-07T16:45:52+00:00"
},
{
"name": "chillerlan/php-qrcode",
@ -300,16 +300,16 @@
},
{
"name": "colinmollenhour/credis",
"version": "v1.13.1",
"version": "v1.14.0",
"source": {
"type": "git",
"url": "https://github.com/colinmollenhour/credis.git",
"reference": "85df015088e00daf8ce395189de22c8eb45c8d49"
"reference": "dccc8a46586475075fbb012d8bd523b8a938c2dc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/colinmollenhour/credis/zipball/85df015088e00daf8ce395189de22c8eb45c8d49",
"reference": "85df015088e00daf8ce395189de22c8eb45c8d49",
"url": "https://api.github.com/repos/colinmollenhour/credis/zipball/dccc8a46586475075fbb012d8bd523b8a938c2dc",
"reference": "dccc8a46586475075fbb012d8bd523b8a938c2dc",
"shasum": ""
},
"require": {
@ -341,9 +341,9 @@
"homepage": "https://github.com/colinmollenhour/credis",
"support": {
"issues": "https://github.com/colinmollenhour/credis/issues",
"source": "https://github.com/colinmollenhour/credis/tree/v1.13.1"
"source": "https://github.com/colinmollenhour/credis/tree/v1.14.0"
},
"time": "2022-06-20T22:56:59+00:00"
"time": "2022-11-09T01:18:39+00:00"
},
{
"name": "dragonmantank/cron-expression",
@ -803,6 +803,72 @@
},
"time": "2020-12-26T17:45:17+00:00"
},
{
"name": "laravel/pint",
"version": "v1.2.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
"reference": "1d276e4c803397a26cc337df908f55c2a4e90d86"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pint/zipball/1d276e4c803397a26cc337df908f55c2a4e90d86",
"reference": "1d276e4c803397a26cc337df908f55c2a4e90d86",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-mbstring": "*",
"ext-tokenizer": "*",
"ext-xml": "*",
"php": "^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.11.0",
"illuminate/view": "^9.27",
"laravel-zero/framework": "^9.1.3",
"mockery/mockery": "^1.5.0",
"nunomaduro/larastan": "^2.2",
"nunomaduro/termwind": "^1.14.0",
"pestphp/pest": "^1.22.1"
},
"bin": [
"builds/pint"
],
"type": "project",
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Seeders\\": "database/seeders/",
"Database\\Factories\\": "database/factories/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nuno Maduro",
"email": "enunomaduro@gmail.com"
}
],
"description": "An opinionated code formatter for PHP.",
"homepage": "https://laravel.com",
"keywords": [
"format",
"formatter",
"lint",
"linter",
"php"
],
"support": {
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
"time": "2022-09-13T15:07:15+00:00"
},
{
"name": "matomo/device-detector",
"version": "6.0.0",
@ -1667,16 +1733,16 @@
},
{
"name": "utopia-php/cli",
"version": "0.13.0",
"version": "0.14.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/cli.git",
"reference": "69e68f8ed525fe162fae950a0507ed28a0f179bc"
"reference": "c30ef985a4e739758a0d95eb0706b357b6d8c086"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/cli/zipball/69e68f8ed525fe162fae950a0507ed28a0f179bc",
"reference": "69e68f8ed525fe162fae950a0507ed28a0f179bc",
"url": "https://api.github.com/repos/utopia-php/cli/zipball/c30ef985a4e739758a0d95eb0706b357b6d8c086",
"reference": "c30ef985a4e739758a0d95eb0706b357b6d8c086",
"shasum": ""
},
"require": {
@ -1685,7 +1751,7 @@
},
"require-dev": {
"phpunit/phpunit": "^9.3",
"vimeo/psalm": "4.0.1"
"squizlabs/php_codesniffer": "^3.6"
},
"type": "library",
"autoload": {
@ -1714,9 +1780,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/cli/issues",
"source": "https://github.com/utopia-php/cli/tree/0.13.0"
"source": "https://github.com/utopia-php/cli/tree/0.14.0"
},
"time": "2022-04-26T08:41:22+00:00"
"time": "2022-10-09T10:19:07+00:00"
},
{
"name": "utopia-php/config",
@ -1879,16 +1945,16 @@
},
{
"name": "utopia-php/framework",
"version": "0.23.4",
"version": "0.25.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/framework.git",
"reference": "97f64aa1732af92b967c3576f16967dc762ad47b"
"reference": "c524f681254255c8204fbf7919c53bf3b4982636"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/97f64aa1732af92b967c3576f16967dc762ad47b",
"reference": "97f64aa1732af92b967c3576f16967dc762ad47b",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/c524f681254255c8204fbf7919c53bf3b4982636",
"reference": "c524f681254255c8204fbf7919c53bf3b4982636",
"shasum": ""
},
"require": {
@ -1916,9 +1982,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/framework/issues",
"source": "https://github.com/utopia-php/framework/tree/0.23.4"
"source": "https://github.com/utopia-php/framework/tree/0.25.0"
},
"time": "2022-10-31T11:57:14+00:00"
"time": "2022-11-02T09:49:57+00:00"
},
{
"name": "utopia-php/image",
@ -2091,21 +2157,21 @@
},
{
"name": "utopia-php/orchestration",
"version": "0.6.0",
"version": "0.9.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/orchestration.git",
"reference": "94263976413871efb6b16157a7101a81df3b6d78"
"reference": "1d4f66684b8c4927f31b695817eae6d84aafd172"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/orchestration/zipball/94263976413871efb6b16157a7101a81df3b6d78",
"reference": "94263976413871efb6b16157a7101a81df3b6d78",
"url": "https://api.github.com/repos/utopia-php/orchestration/zipball/1d4f66684b8c4927f31b695817eae6d84aafd172",
"reference": "1d4f66684b8c4927f31b695817eae6d84aafd172",
"shasum": ""
},
"require": {
"php": ">=8.0",
"utopia-php/cli": "0.13.*"
"utopia-php/cli": "0.14.*"
},
"require-dev": {
"phpunit/phpunit": "^9.3",
@ -2121,12 +2187,6 @@
"license": [
"MIT"
],
"authors": [
{
"name": "Eldad Fux",
"email": "eldad@appwrite.io"
}
],
"description": "Lite & fast micro PHP abstraction library for container orchestration",
"keywords": [
"docker",
@ -2140,34 +2200,80 @@
],
"support": {
"issues": "https://github.com/utopia-php/orchestration/issues",
"source": "https://github.com/utopia-php/orchestration/tree/0.6.0"
"source": "https://github.com/utopia-php/orchestration/tree/0.9.0"
},
"time": "2022-07-13T16:47:18+00:00"
"time": "2022-11-09T17:38:00+00:00"
},
{
"name": "utopia-php/pools",
"version": "dev-feat-optimize-filling",
"name": "utopia-php/platform",
"version": "0.3.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/pools.git",
"reference": "be603898142f55df9db5058f81a610ead7769d7d"
"url": "https://github.com/utopia-php/platform.git",
"reference": "fe9f64420957dc8fb6201d22b499572f021411e4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/pools/zipball/be603898142f55df9db5058f81a610ead7769d7d",
"reference": "be603898142f55df9db5058f81a610ead7769d7d",
"url": "https://api.github.com/repos/utopia-php/platform/zipball/fe9f64420957dc8fb6201d22b499572f021411e4",
"reference": "fe9f64420957dc8fb6201d22b499572f021411e4",
"shasum": ""
},
"require": {
"ext-mongodb": "*",
"ext-pdo": "*",
"ext-json": "*",
"ext-redis": "*",
"php": ">=8.0",
"utopia-php/cli": "^0.13.0"
"utopia-php/cli": "0.14.*",
"utopia-php/framework": "0.25.*"
},
"require-dev": {
"phpunit/phpunit": "^9.4",
"vimeo/psalm": "4.0.1"
"phpunit/phpunit": "^9.3",
"squizlabs/php_codesniffer": "^3.6"
},
"type": "library",
"autoload": {
"psr-4": {
"Utopia\\Platform\\": "src/Platform"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Light and Fast Platform Library",
"keywords": [
"cache",
"framework",
"php",
"upf",
"utopia"
],
"support": {
"issues": "https://github.com/utopia-php/platform/issues",
"source": "https://github.com/utopia-php/platform/tree/0.3.1"
},
"time": "2022-11-10T07:04:24+00:00"
},
{
"name": "utopia-php/pools",
"version": "0.4.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/pools.git",
"reference": "c8f96a33e7fbf58c1145eb6cf0f2c00cbe319979"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/pools/zipball/c8f96a33e7fbf58c1145eb6cf0f2c00cbe319979",
"reference": "c8f96a33e7fbf58c1145eb6cf0f2c00cbe319979",
"shasum": ""
},
"require": {
"php": ">=8.0"
},
"require-dev": {
"laravel/pint": "1.2.*",
"phpstan/phpstan": "1.8.*",
"phpunit/phpunit": "^9.3"
},
"type": "library",
"autoload": {
@ -2194,9 +2300,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/pools/issues",
"source": "https://github.com/utopia-php/pools/tree/feat-optimize-filling"
"source": "https://github.com/utopia-php/pools/tree/0.4.1"
},
"time": "2022-11-03T18:50:28+00:00"
"time": "2022-11-15T08:55:16+00:00"
},
{
"name": "utopia-php/preloader",
@ -2253,16 +2359,16 @@
},
{
"name": "utopia-php/registry",
"version": "0.5.0",
"version": "dev-feat-allow-params",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/registry.git",
"reference": "bedc4ed54527b2803e6dfdccc39449f98522b70d"
"reference": "6c571f8f4127094b3af8909d1b595fd6b937255d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/registry/zipball/bedc4ed54527b2803e6dfdccc39449f98522b70d",
"reference": "bedc4ed54527b2803e6dfdccc39449f98522b70d",
"url": "https://api.github.com/repos/utopia-php/registry/zipball/6c571f8f4127094b3af8909d1b595fd6b937255d",
"reference": "6c571f8f4127094b3af8909d1b595fd6b937255d",
"shasum": ""
},
"require": {
@ -2299,9 +2405,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/registry/issues",
"source": "https://github.com/utopia-php/registry/tree/0.5.0"
"source": "https://github.com/utopia-php/registry/tree/feat-allow-params"
},
"time": "2021-03-10T10:45:22+00:00"
"time": "2022-11-09T02:23:35+00:00"
},
{
"name": "utopia-php/storage",
@ -2411,23 +2517,25 @@
},
{
"name": "utopia-php/system",
"version": "0.4.0",
"version": "0.6.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/system.git",
"reference": "67c92c66ce8f0cc925a00bca89f7a188bf9183c0"
"reference": "289c4327713deadc9c748b5317d248133a02f245"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/system/zipball/67c92c66ce8f0cc925a00bca89f7a188bf9183c0",
"reference": "67c92c66ce8f0cc925a00bca89f7a188bf9183c0",
"url": "https://api.github.com/repos/utopia-php/system/zipball/289c4327713deadc9c748b5317d248133a02f245",
"reference": "289c4327713deadc9c748b5317d248133a02f245",
"shasum": ""
},
"require": {
"laravel/pint": "1.2.*",
"php": ">=7.4"
},
"require-dev": {
"phpunit/phpunit": "^9.3",
"squizlabs/php_codesniffer": "^3.6",
"vimeo/psalm": "4.0.1"
},
"type": "library",
@ -2460,9 +2568,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/system/issues",
"source": "https://github.com/utopia-php/system/tree/0.4.0"
"source": "https://github.com/utopia-php/system/tree/0.6.0"
},
"time": "2021-02-04T14:14:49+00:00"
"time": "2022-11-07T13:51:59+00:00"
},
{
"name": "utopia-php/websocket",
@ -2886,16 +2994,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.15.1",
"version": "v4.15.2",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900"
"reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900",
"reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
"reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
"shasum": ""
},
"require": {
@ -2936,9 +3044,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2"
},
"time": "2022-09-04T07:30:47+00:00"
"time": "2022-11-12T15:38:23+00:00"
},
{
"name": "phar-io/manifest",
@ -4768,16 +4876,16 @@
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.26.0",
"version": "v1.27.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4"
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a",
"reference": "5bbc823adecdae860bb64756d639ecfec17b050a",
"shasum": ""
},
"require": {
@ -4792,7 +4900,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.26-dev"
"dev-main": "1.27-dev"
},
"thanks": {
"name": "symfony/polyfill",
@ -4830,7 +4938,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0"
},
"funding": [
{
@ -4846,20 +4954,20 @@
"type": "tidelift"
}
],
"time": "2022-05-24T11:49:31+00:00"
"time": "2022-11-03T14:55:06+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.26.0",
"version": "v1.27.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e"
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
"reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
"reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534",
"shasum": ""
},
"require": {
@ -4874,7 +4982,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.26-dev"
"dev-main": "1.27-dev"
},
"thanks": {
"name": "symfony/polyfill",
@ -4913,7 +5021,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0"
},
"funding": [
{
@ -4929,7 +5037,7 @@
"type": "tidelift"
}
],
"time": "2022-05-24T11:49:31+00:00"
"time": "2022-11-03T14:55:06+00:00"
},
{
"name": "textalk/websocket",
@ -5109,15 +5217,15 @@
],
"aliases": [
{
"package": "utopia-php/database",
"version": "0.28.0.0",
"alias": "0.26.99",
"alias_normalized": "0.26.99.0"
"package": "utopia-php/registry",
"version": "dev-feat-allow-params",
"alias": "0.5.0",
"alias_normalized": "0.5.0.0"
}
],
"minimum-stability": "stable",
"stability-flags": {
"utopia-php/pools": 20
"utopia-php/registry": 20
},
"prefer-stable": false,
"prefer-lowest": false,

View file

@ -14,7 +14,7 @@ version: '3'
services:
traefik:
image: traefik:2.7
image: traefik:2.9
<<: *x-logging
container_name: appwrite-traefik
command:
@ -33,6 +33,15 @@ services:
- 8080:80
- 443:443
- 9500:8080
ulimits:
nofile:
soft: 655350
hard: 655350
sysctls:
- net.core.somaxconn=1024
- net.ipv4.tcp_rmem=1024 4096 16384
- net.ipv4.tcp_wmem=1024 4096 16384
- net.ipv4.ip_local_port_range=1025 65535
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- appwrite-config:/storage/config:ro
@ -76,7 +85,7 @@ services:
- appwrite-cache:/storage/cache:rw
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
- appwrite-functions:/storage/functions:rw
- openruntimes-functions:/storage/functions:rw
- ./phpunit.xml:/usr/src/code/phpunit.xml
- ./tests:/usr/src/code/tests
- ./app:/usr/src/code/app
@ -114,6 +123,7 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_MAX_CONNECTIONS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@ -160,10 +170,8 @@ services:
- _APP_FUNCTIONS_SIZE_LIMIT
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CONTAINERS
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_MEMORY_SWAP
- _APP_FUNCTIONS_RUNTIMES
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
@ -221,6 +229,7 @@ services:
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DB_MAX_CONNECTIONS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
@ -229,6 +238,7 @@ services:
- _APP_CONNECTIONS_DB_PROJECT
- _APP_CONNECTIONS_CACHE
- _APP_CONNECTIONS_PUBSUB
- _APP_CONNECTIONS_QUEUE
- _APP_USAGE_STATS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
@ -304,8 +314,8 @@ services:
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw
- appwrite-functions:/storage/functions:rw
- appwrite-builds:/storage/builds:rw
- openruntimes-functions:/storage/functions:rw
- openruntimes-builds:/storage/builds:rw
- appwrite-certificates:/storage/certificates:rw
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
@ -467,7 +477,7 @@ services:
depends_on:
- redis
- mariadb
- appwrite-executor
- openruntimes-executor
environment:
- _APP_ENV
- _APP_OPENSSL_KEY_V1
@ -491,67 +501,6 @@ services:
- DOCKERHUB_PULL_USERNAME
- DOCKERHUB_PULL_PASSWORD
appwrite-executor:
container_name: appwrite-executor
<<: *x-logging
entrypoint: executor
stop_signal: SIGINT
image: appwrite-dev
networks:
appwrite:
runtimes:
ports:
- 9519:80
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
- appwrite-functions:/storage/functions:rw
- appwrite-builds:/storage/builds:rw
- /tmp:/tmp:rw
depends_on:
- redis
- mariadb
- appwrite
environment:
- _APP_ENV
- _APP_VERSION
- _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CONTAINERS
- _APP_FUNCTIONS_RUNTIMES
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_MEMORY_SWAP
- _APP_FUNCTIONS_INACTIVE_THRESHOLD
- _APP_EXECUTOR_SECRET
- OPEN_RUNTIMES_NETWORK
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
- _APP_STORAGE_S3_REGION
- _APP_STORAGE_S3_BUCKET
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
- _APP_STORAGE_DO_SPACES_SECRET
- _APP_STORAGE_DO_SPACES_REGION
- _APP_STORAGE_DO_SPACES_BUCKET
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY
- _APP_STORAGE_BACKBLAZE_SECRET
- _APP_STORAGE_BACKBLAZE_REGION
- _APP_STORAGE_BACKBLAZE_BUCKET
- _APP_STORAGE_LINODE_ACCESS_KEY
- _APP_STORAGE_LINODE_SECRET
- _APP_STORAGE_LINODE_REGION
- _APP_STORAGE_LINODE_BUCKET
- _APP_STORAGE_WASABI_ACCESS_KEY
- _APP_STORAGE_WASABI_SECRET
- _APP_STORAGE_WASABI_REGION
- _APP_STORAGE_WASABI_BUCKET
- DOCKERHUB_PULL_USERNAME
- DOCKERHUB_PULL_PASSWORD
appwrite-worker-mails:
entrypoint: worker-mails
<<: *x-logging
@ -762,6 +711,32 @@ services:
- _APP_CONNECTIONS_QUEUE
- _APP_REGION
openruntimes-executor:
container_name: openruntimes-executor
hostname: exc1
<<: *x-logging
stop_signal: SIGINT
image: openruntimes/executor:0.1.4
networks:
- appwrite
- openruntimes-runtimes
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- openruntimes-builds:/storage/builds:rw
- openruntimes-functions:/storage/functions:rw
- /tmp:/tmp:rw
environment:
- OPR_EXECUTOR_CONNECTION_STORAGE=$_APP_FUNCTIONS_CONNECTION_STORAGE
- OPR_EXECUTOR_INACTIVE_TRESHOLD=$_APP_FUNCTIONS_INACTIVE_THRESHOLD
- OPR_EXECUTOR_NETWORK=$_APP_FUNCTIONS_RUNTIMES_NETWORK
- OPR_EXECUTOR_DOCKER_HUB_USERNAME=$_APP_DOCKER_HUB_USERNAME
- OPR_EXECUTOR_DOCKER_HUB_PASSWORD=$_APP_DOCKER_HUB_PASSWORD
- OPR_EXECUTOR_ENV=$_APP_ENV
- OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES
- OPR_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
- OPR_EXECUTOR_LOGGING_PROVIDER=$_APP_LOGGING_PROVIDER
- OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
mariadb:
image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
@ -777,8 +752,7 @@ services:
- MYSQL_DATABASE=${_APP_DB_SCHEMA}
- MYSQL_USER=${_APP_DB_USER}
- MYSQL_PASSWORD=${_APP_DB_PASS}
command: 'mysqld --innodb-flush-method=fsync' # add ' --query_cache_size=0' for DB tests
# command: mv /var/lib/mysql/ib_logfile0 /var/lib/mysql/ib_logfile0.bu && mv /var/lib/mysql/ib_logfile1 /var/lib/mysql/ib_logfile1.bu
command: 'mysqld --innodb-flush-method=fsync --max_connections=${_APP_DB_MAX_CONNECTIONS}'
# smtp:
# image: appwrite/smtp:1.2.0
@ -924,8 +898,11 @@ services:
networks:
gateway:
name: gateway
appwrite:
runtimes:
name: appwrite
openruntimes-runtimes:
name: openruntimes-runtimes
volumes:
appwrite-mariadb:
@ -933,9 +910,8 @@ volumes:
appwrite-cache:
appwrite-uploads:
appwrite-certificates:
appwrite-functions:
appwrite-builds:
appwrite-influxdb:
appwrite-config:
appwrite-executor:
openruntimes-functions:
openruntimes-builds:
# appwrite-chronograf:

File diff suppressed because one or more lines are too long

View file

@ -12,7 +12,7 @@ Service.CHUNK_SIZE=5*1024*1024;class Query{}
Query.equal=(attribute,value)=>Query.addQuery(attribute,"equal",value);Query.notEqual=(attribute,value)=>Query.addQuery(attribute,"notEqual",value);Query.lessThan=(attribute,value)=>Query.addQuery(attribute,"lessThan",value);Query.lessThanEqual=(attribute,value)=>Query.addQuery(attribute,"lessThanEqual",value);Query.greaterThan=(attribute,value)=>Query.addQuery(attribute,"greaterThan",value);Query.greaterThanEqual=(attribute,value)=>Query.addQuery(attribute,"greaterThanEqual",value);Query.search=(attribute,value)=>Query.addQuery(attribute,"search",value);Query.orderDesc=(attribute)=>`orderDesc("${attribute}")`;Query.orderAsc=(attribute)=>`orderAsc("${attribute}")`;Query.cursorAfter=(documentId)=>`cursorAfter("${documentId}")`;Query.cursorBefore=(documentId)=>`cursorBefore("${documentId}")`;Query.limit=(limit)=>`limit(${limit})`;Query.offset=(offset)=>`offset(${offset})`;Query.addQuery=(attribute,method,value)=>value instanceof Array?`${method}("${attribute}", [${value
.map((v) => Query.parseValues(v))
.join(",")}])`:`${method}("${attribute}", [${Query.parseValues(value)}])`;Query.parseValues=(value)=>typeof value==="string"||value instanceof String?`"${value}"`:`${value}`;class AppwriteException extends Error{constructor(message,code=0,type='',response=''){super(message);this.name='AppwriteException';this.message=message;this.code=code;this.type=type;this.response=response;}}
class Client{constructor(){this.config={endpoint:'https://HOSTNAME/v1',endpointRealtime:'',project:'',key:'',jwt:'',locale:'',mode:'',};this.headers={'x-sdk-name':'Console','x-sdk-platform':'console','x-sdk-language':'web','x-sdk-version':'7.0.0','X-Appwrite-Response-Format':'1.0.0',};this.realtime={socket:undefined,timeout:undefined,url:'',channels:new Set(),subscriptions:new Map(),subscriptionsCounter:0,reconnect:true,reconnectAttempts:0,lastMessage:undefined,connect:()=>{clearTimeout(this.realtime.timeout);this.realtime.timeout=window===null||window===void 0?void 0:window.setTimeout(()=>{this.realtime.createSocket();},50);},getTimeout:()=>{switch(true){case this.realtime.reconnectAttempts<5:return 1000;case this.realtime.reconnectAttempts<15:return 5000;case this.realtime.reconnectAttempts<100:return 10000;default:return 60000;}},createSocket:()=>{var _a,_b;if(this.realtime.channels.size<1)
class Client{constructor(){this.config={endpoint:'https://HOSTNAME/v1',endpointRealtime:'',project:'',key:'',jwt:'',locale:'',mode:'',};this.headers={'x-sdk-name':'Console','x-sdk-platform':'console','x-sdk-language':'web','x-sdk-version':'7.1.0','X-Appwrite-Response-Format':'1.0.0',};this.realtime={socket:undefined,timeout:undefined,url:'',channels:new Set(),subscriptions:new Map(),subscriptionsCounter:0,reconnect:true,reconnectAttempts:0,lastMessage:undefined,connect:()=>{clearTimeout(this.realtime.timeout);this.realtime.timeout=window===null||window===void 0?void 0:window.setTimeout(()=>{this.realtime.createSocket();},50);},getTimeout:()=>{switch(true){case this.realtime.reconnectAttempts<5:return 1000;case this.realtime.reconnectAttempts<15:return 5000;case this.realtime.reconnectAttempts<100:return 10000;default:return 60000;}},createSocket:()=>{var _a,_b;if(this.realtime.channels.size<1)
return;const channels=new URLSearchParams();channels.set('project',this.config.project);this.realtime.channels.forEach(channel=>{channels.append('channels[]',channel);});const url=this.config.endpointRealtime+'/realtime?'+channels.toString();if(url!==this.realtime.url||!this.realtime.socket||((_a=this.realtime.socket)===null||_a===void 0?void 0:_a.readyState)>WebSocket.OPEN){if(this.realtime.socket&&((_b=this.realtime.socket)===null||_b===void 0?void 0:_b.readyState)<WebSocket.CLOSING){this.realtime.reconnect=false;this.realtime.socket.close();}
this.realtime.url=url;this.realtime.socket=new WebSocket(url);this.realtime.socket.addEventListener('message',this.realtime.onMessage);this.realtime.socket.addEventListener('open',_event=>{this.realtime.reconnectAttempts=0;});this.realtime.socket.addEventListener('close',event=>{var _a,_b,_c;if(!this.realtime.reconnect||(((_b=(_a=this.realtime)===null||_a===void 0?void 0:_a.lastMessage)===null||_b===void 0?void 0:_b.type)==='error'&&((_c=this.realtime)===null||_c===void 0?void 0:_c.lastMessage.data).code===1008)){this.realtime.reconnect=true;return;}
const timeout=this.realtime.getTimeout();console.error(`Realtime got disconnected. Reconnect will be attempted in ${timeout / 1000} seconds.`,event.reason);setTimeout(()=>{this.realtime.reconnectAttempts++;this.realtime.createSocket();},timeout);});}},onMessage:(event)=>{var _a,_b;try{const message=JSON.parse(event.data);this.realtime.lastMessage=message;switch(message.type){case'connected':const cookie=JSON.parse((_a=window.localStorage.getItem('cookieFallback'))!==null&&_a!==void 0?_a:'{}');const session=cookie===null||cookie===void 0?void 0:cookie[`a_session_${this.config.project}`];const messageData=message.data;if(session&&!messageData.user){(_b=this.realtime.socket)===null||_b===void 0?void 0:_b.send(JSON.stringify({type:'authentication',data:{session}}));}
@ -458,7 +458,7 @@ let path='/functions/{functionId}/deployments/{deploymentId}'.replace('{function
deleteDeployment(functionId,deploymentId){return __awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
if(typeof deploymentId==='undefined'){throw new AppwriteException('Missing required parameter: "deploymentId"');}
let path='/functions/{functionId}/deployments/{deploymentId}'.replace('{functionId}',functionId).replace('{deploymentId}',deploymentId);let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('delete',uri,{'content-type':'application/json',},payload);});}
retryBuild(functionId,deploymentId,buildId){return __awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
createBuild(functionId,deploymentId,buildId){return __awaiter(this,void 0,void 0,function*(){if(typeof functionId==='undefined'){throw new AppwriteException('Missing required parameter: "functionId"');}
if(typeof deploymentId==='undefined'){throw new AppwriteException('Missing required parameter: "deploymentId"');}
if(typeof buildId==='undefined'){throw new AppwriteException('Missing required parameter: "buildId"');}
let path='/functions/{functionId}/deployments/{deploymentId}/builds/{buildId}'.replace('{functionId}',functionId).replace('{deploymentId}',deploymentId).replace('{buildId}',buildId);let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('post',uri,{'content-type':'application/json',},payload);});}
@ -501,6 +501,8 @@ get(){return __awaiter(this,void 0,void 0,function*(){let path='/health';let pay
getAntivirus(){return __awaiter(this,void 0,void 0,function*(){let path='/health/anti-virus';let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
getCache(){return __awaiter(this,void 0,void 0,function*(){let path='/health/cache';let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
getDB(){return __awaiter(this,void 0,void 0,function*(){let path='/health/db';let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
getPubSub(){return __awaiter(this,void 0,void 0,function*(){let path='/health/pubsub';let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
getQueue(){return __awaiter(this,void 0,void 0,function*(){let path='/health/queue';let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
getQueueCertificates(){return __awaiter(this,void 0,void 0,function*(){let path='/health/queue/certificates';let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
getQueueFunctions(){return __awaiter(this,void 0,void 0,function*(){let path='/health/queue/functions';let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
getQueueLogs(){return __awaiter(this,void 0,void 0,function*(){let path='/health/queue/logs';let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
@ -515,6 +517,9 @@ listCountriesEU(){return __awaiter(this,void 0,void 0,function*(){let path='/loc
listCountriesPhones(){return __awaiter(this,void 0,void 0,function*(){let path='/locale/countries/phones';let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
listCurrencies(){return __awaiter(this,void 0,void 0,function*(){let path='/locale/currencies';let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
listLanguages(){return __awaiter(this,void 0,void 0,function*(){let path='/locale/languages';let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}}
class Project extends Service{constructor(client){super(client);}
getUsage(range){return __awaiter(this,void 0,void 0,function*(){let path='/project/usage';let payload={};if(typeof range!=='undefined'){payload['range']=range;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}}
class Projects extends Service{constructor(client){super(client);}
list(queries,search){return __awaiter(this,void 0,void 0,function*(){let path='/projects';let payload={};if(typeof queries!=='undefined'){payload['queries']=queries;}
if(typeof search!=='undefined'){payload['search']=search;}
@ -638,9 +643,6 @@ if(typeof status==='undefined'){throw new AppwriteException('Missing required pa
let path='/projects/{projectId}/service'.replace('{projectId}',projectId);let payload={};if(typeof service!=='undefined'){payload['service']=service;}
if(typeof status!=='undefined'){payload['status']=status;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('patch',uri,{'content-type':'application/json',},payload);});}
getUsage(projectId,range){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
let path='/projects/{projectId}/usage'.replace('{projectId}',projectId);let payload={};if(typeof range!=='undefined'){payload['range']=range;}
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
listWebhooks(projectId){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
let path='/projects/{projectId}/webhooks'.replace('{projectId}',projectId);let payload={};const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('get',uri,{'content-type':'application/json',},payload);});}
createWebhook(projectId,name,events,url,security,httpUser,httpPass){return __awaiter(this,void 0,void 0,function*(){if(typeof projectId==='undefined'){throw new AppwriteException('Missing required parameter: "projectId"');}
@ -957,15 +959,17 @@ let path='/users/{userId}/verification/phone'.replace('{userId}',userId);let pay
const uri=new URL(this.client.config.endpoint+path);return yield this.client.call('patch',uri,{'content-type':'application/json',},payload);});}}
class Permission{}
Permission.read=(role)=>{return`read("${role}")`;};Permission.write=(role)=>{return`write("${role}")`;};Permission.create=(role)=>{return`create("${role}")`;};Permission.update=(role)=>{return`update("${role}")`;};Permission.delete=(role)=>{return`delete("${role}")`;};class Role{static any(){return'any';}
static user(id){return`user:${id}`;}
static users(){return'users';}
static user(id,status=''){if(status===''){return`user:${id}`;}
return`user:${id}/${status}`;}
static users(status=''){if(status===''){return'users';}
return`users/${status}`;}
static guests(){return'guests';}
static team(id,role=''){if(role===''){return`team:${id}`;}
return`team:${id}/${role}`;}
static status(status){return`status:${status}`;}}
static member(id){return`member:${id}`;}}
class ID{static custom(id){return id;}
static unique(){return'unique()';}}
exports.Account=Account;exports.AppwriteException=AppwriteException;exports.Avatars=Avatars;exports.Client=Client;exports.Databases=Databases;exports.Functions=Functions;exports.Health=Health;exports.ID=ID;exports.Locale=Locale;exports.Permission=Permission;exports.Projects=Projects;exports.Query=Query;exports.Role=Role;exports.Storage=Storage;exports.Teams=Teams;exports.Users=Users;Object.defineProperty(exports,'__esModule',{value:true});})(this.Appwrite=this.Appwrite||{},null,window);(function(global,factory){typeof exports==='object'&&typeof module!=='undefined'?module.exports=factory():typeof define==='function'&&define.amd?define(factory):(global=typeof globalThis!=='undefined'?globalThis:global||self,global.Chart=factory());})(this,(function(){'use strict';function noop(){}
exports.Account=Account;exports.AppwriteException=AppwriteException;exports.Avatars=Avatars;exports.Client=Client;exports.Databases=Databases;exports.Functions=Functions;exports.Health=Health;exports.ID=ID;exports.Locale=Locale;exports.Permission=Permission;exports.Project=Project;exports.Projects=Projects;exports.Query=Query;exports.Role=Role;exports.Storage=Storage;exports.Teams=Teams;exports.Users=Users;Object.defineProperty(exports,'__esModule',{value:true});})(this.Appwrite=this.Appwrite||{},null,window);(function(global,factory){typeof exports==='object'&&typeof module!=='undefined'?module.exports=factory():typeof define==='function'&&define.amd?define(factory):(global=typeof globalThis!=='undefined'?globalThis:global||self,global.Chart=factory());})(this,(function(){'use strict';function noop(){}
const uid=(function(){let id=0;return function(){return id++;};}());function isNullOrUndef(value){return value===null||typeof value==='undefined';}
function isArray(value){if(Array.isArray&&Array.isArray(value)){return true;}
const type=Object.prototype.toString.call(value);if(type.slice(0,7)==='[object'&&type.slice(-6)==='Array]'){return true;}

File diff suppressed because one or more lines are too long

View file

@ -96,7 +96,7 @@
'x-sdk-name': 'Console',
'x-sdk-platform': 'console',
'x-sdk-language': 'web',
'x-sdk-version': '7.0.0',
'x-sdk-version': '7.1.0',
'X-Appwrite-Response-Format': '1.0.0',
};
this.realtime = {
@ -500,7 +500,7 @@
});
}
/**
* Update Account Email
* Update Email
*
* Update currently logged in user account email address. After changing user
* address, the user confirmation status will get reset. A new confirmation
@ -539,7 +539,7 @@
});
}
/**
* Create Account JWT
* Create JWT
*
* Use this endpoint to create a JSON Web Token. You can use the resulting JWT
* to authenticate on behalf of the current user when working with the
@ -561,7 +561,7 @@
});
}
/**
* List Account Logs
* List Logs
*
* Get currently logged in user list of latest security activity logs. Each
* log returns user IP address, location and date and time of log.
@ -584,7 +584,7 @@
});
}
/**
* Update Account Name
* Update Name
*
* Update currently logged in user account name.
*
@ -609,7 +609,7 @@
});
}
/**
* Update Account Password
* Update Password
*
* Update currently logged in user password. For validation, user is required
* to pass in the new password, and the old password. For users created with
@ -640,7 +640,7 @@
});
}
/**
* Update Account Phone
* Update Phone
*
* Update the currently logged in user's phone number. After updating the
* phone number, the phone verification status will be reset. A confirmation
@ -694,7 +694,7 @@
});
}
/**
* Update Account Preferences
* Update Preferences
*
* Update currently logged in user account preferences. The object you pass is
* stored as is, and replaces any previous value. The maximum allowed prefs
@ -814,7 +814,7 @@
});
}
/**
* List Account Sessions
* List Sessions
*
* Get currently logged in user list of active sessions across different
* devices.
@ -833,7 +833,7 @@
});
}
/**
* Delete All Account Sessions
* Delete Sessions
*
* Delete all sessions from the user account and remove any sessions cookies
* from the end client.
@ -875,7 +875,7 @@
});
}
/**
* Create Account Session with Email
* Create Email Session
*
* Allow the user to login into their account by providing a valid email and
* password combination. This route will create a new session for the user.
@ -996,7 +996,7 @@
});
}
/**
* Create Account Session with OAuth2
* Create OAuth2 Session
*
* Allow the user to login to their account using the OAuth2 provider of their
* choice. Each OAuth2 provider should be enabled from the Appwrite console
@ -1119,7 +1119,7 @@
});
}
/**
* Get Session By ID
* Get Session
*
* Use this endpoint to get a logged in user's session using a Session ID.
* Inputting 'current' will return the current session being used.
@ -1142,7 +1142,7 @@
});
}
/**
* Update Session (Refresh Tokens)
* Update OAuth Session (Refresh Tokens)
*
* Access tokens have limited lifespan and expire to mitigate security risks.
* If session was created using an OAuth provider, this route can be used to
@ -1166,7 +1166,7 @@
});
}
/**
* Delete Account Session
* Delete Session
*
* Use this endpoint to log out the currently logged in user from all their
* account sessions across all of their different devices. When using the
@ -1191,7 +1191,7 @@
});
}
/**
* Update Account Status
* Update Status
*
* Block the currently logged in user account. Behind the scene, the user
* record is not deleted but permanently blocked from any access. To
@ -2527,9 +2527,7 @@
* List Documents
*
* Get a list of all the user's documents in a given collection. You can use
* the query params to filter your results. On admin mode, this endpoint will
* return a list of all of documents belonging to the provided collectionId.
* [Learn more about different API modes](/docs/admin).
* the query params to filter your results.
*
* @param {string} databaseId
* @param {string} collectionId
@ -3413,7 +3411,7 @@
});
}
/**
* Retry Build
* Create Build
*
*
* @param {string} functionId
@ -3422,7 +3420,7 @@
* @throws {AppwriteException}
* @returns {Promise}
*/
retryBuild(functionId, deploymentId, buildId) {
createBuild(functionId, deploymentId, buildId) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof functionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "functionId"');
@ -3445,9 +3443,7 @@
* List Executions
*
* Get a list of all the current user function execution logs. You can use the
* query params to filter your results. On admin mode, this endpoint will
* return a list of all of the project's executions. [Learn more about
* different API modes](/docs/admin).
* query params to filter your results.
*
* @param {string} functionId
* @param {string[]} queries
@ -3751,7 +3747,7 @@
/**
* Get Cache
*
* Check the Appwrite in-memory cache server is up and connection is
* Check the Appwrite in-memory cache servers are up and connection is
* successful.
*
* @throws {AppwriteException}
@ -3770,7 +3766,7 @@
/**
* Get DB
*
* Check the Appwrite database server is up and connection is successful.
* Check the Appwrite database servers are up and connection is successful.
*
* @throws {AppwriteException}
* @returns {Promise}
@ -3785,6 +3781,43 @@
}, payload);
});
}
/**
* Get PubSub
*
* Check the Appwrite pub-sub servers are up and connection is successful.
*
* @throws {AppwriteException}
* @returns {Promise}
*/
getPubSub() {
return __awaiter(this, void 0, void 0, function* () {
let path = '/health/pubsub';
let payload = {};
const uri = new URL(this.client.config.endpoint + path);
return yield this.client.call('get', uri, {
'content-type': 'application/json',
}, payload);
});
}
/**
* Get Queue
*
* Check the Appwrite queue messaging servers are up and connection is
* successful.
*
* @throws {AppwriteException}
* @returns {Promise}
*/
getQueue() {
return __awaiter(this, void 0, void 0, function* () {
let path = '/health/queue';
let payload = {};
const uri = new URL(this.client.config.endpoint + path);
return yield this.client.call('get', uri, {
'content-type': 'application/json',
}, payload);
});
}
/**
* Get Certificates Queue
*
@ -4048,6 +4081,33 @@
}
}
class Project extends Service {
constructor(client) {
super(client);
}
/**
* Get usage stats for a project
*
*
* @param {string} range
* @throws {AppwriteException}
* @returns {Promise}
*/
getUsage(range) {
return __awaiter(this, void 0, void 0, function* () {
let path = '/project/usage';
let payload = {};
if (typeof range !== 'undefined') {
payload['range'] = range;
}
const uri = new URL(this.client.config.endpoint + path);
return yield this.client.call('get', uri, {
'content-type': 'application/json',
}, payload);
});
}
}
class Projects extends Service {
constructor(client) {
super(client);
@ -4834,31 +4894,6 @@
}, payload);
});
}
/**
* Get usage stats for a project
*
*
* @param {string} projectId
* @param {string} range
* @throws {AppwriteException}
* @returns {Promise}
*/
getUsage(projectId, range) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof projectId === 'undefined') {
throw new AppwriteException('Missing required parameter: "projectId"');
}
let path = '/projects/{projectId}/usage'.replace('{projectId}', projectId);
let payload = {};
if (typeof range !== 'undefined') {
payload['range'] = range;
}
const uri = new URL(this.client.config.endpoint + path);
return yield this.client.call('get', uri, {
'content-type': 'application/json',
}, payload);
});
}
/**
* List Webhooks
*
@ -5280,8 +5315,7 @@
* List Files
*
* Get a list of all the user files. You can use the query params to filter
* your results. On admin mode, this endpoint will return a list of all of the
* project's files. [Learn more about different API modes](/docs/admin).
* your results.
*
* @param {string} bucketId
* @param {string[]} queries
@ -5683,9 +5717,6 @@
* Get a list of all the teams in which the current user is a member. You can
* use the parameters to filter your results.
*
* In admin mode, this endpoint returns a list of all the teams in the current
* project. [Learn more about different API modes](/docs/admin).
*
* @param {string[]} queries
* @param {string} search
* @throws {AppwriteException}
@ -7002,11 +7033,17 @@
static any() {
return 'any';
}
static user(id) {
return `user:${id}`;
static user(id, status = '') {
if (status === '') {
return `user:${id}`;
}
return `user:${id}/${status}`;
}
static users() {
return 'users';
static users(status = '') {
if (status === '') {
return 'users';
}
return `users/${status}`;
}
static guests() {
return 'guests';
@ -7017,8 +7054,8 @@
}
return `team:${id}/${role}`;
}
static status(status) {
return `status:${status}`;
static member(id) {
return `member:${id}`;
}
}
@ -7041,6 +7078,7 @@
exports.ID = ID;
exports.Locale = Locale;
exports.Permission = Permission;
exports.Project = Project;
exports.Projects = Projects;
exports.Query = Query;
exports.Role = Role;

View file

@ -14,6 +14,7 @@
return {
client: client,
project: new Appwrite.Project(client),
account: new Appwrite.Account(client),
avatars: new Appwrite.Avatars(client),
databases: new Appwrite.Databases(client),

View file

@ -0,0 +1,14 @@
<?php
namespace Appwrite\Platform;
use Appwrite\Platform\Services\Tasks;
use Utopia\Platform\Platform;
class Appwrite extends Platform
{
public function __construct()
{
$this->addService('tasks', new Tasks());
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Appwrite\Platform\Services;
use Utopia\Platform\Service;
use Appwrite\Platform\Tasks\Doctor;
use Appwrite\Platform\Tasks\Install;
use Appwrite\Platform\Tasks\Maintenance;
use Appwrite\Platform\Tasks\Migrate;
use Appwrite\Platform\Tasks\SDKs;
use Appwrite\Platform\Tasks\Specs;
use Appwrite\Platform\Tasks\SSL;
use Appwrite\Platform\Tasks\Usage;
use Appwrite\Platform\Tasks\Vars;
use Appwrite\Platform\Tasks\Version;
use Appwrite\Platform\Tasks\VolumeSync;
class Tasks extends Service
{
public function __construct()
{
$this->type = self::TYPE_CLI;
$this
->addAction(Version::getName(), new Version())
->addAction(Usage::getName(), new Usage())
->addAction(Vars::getName(), new Vars())
->addAction(SSL::getName(), new SSL())
->addAction(Doctor::getName(), new Doctor())
->addAction(Install::getName(), new Install())
->addAction(Maintenance::getName(), new Maintenance())
->addAction(Migrate::getName(), new Migrate())
->addAction(SDKs::getName(), new SDKs())
->addAction(VolumeSync::getName(), new VolumeSync())
->addAction(Specs::getName(), new Specs());
}
}

View file

@ -1,20 +1,35 @@
<?php
global $cli;
namespace Appwrite\Platform\Tasks;
use Utopia\App;
use Utopia\CLI\Console;
use Appwrite\ClamAV\Network;
use Utopia\Logger\Logger;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Domains\Domain;
use Utopia\Platform\Action;
use Utopia\Registry\Registry;
$cli
->task('doctor')
->desc('Validate server health')
->action(function () use ($register) {
class Doctor extends Action
{
public static function getName(): string
{
return 'doctor';
}
public function __construct()
{
$this
->desc('Validate server health')
->inject('register')
->callback(fn (Registry $register) => $this->action($register));
}
public function action(Registry $register): void
{
Console::log(" __ ____ ____ _ _ ____ __ ____ ____ __ __
/ _\ ( _ \( _ \/ )( \( _ \( )(_ _)( __) ( )/ \
/ \ ) __/ ) __/\ /\ / ) / )( )( ) _) _ )(( O )
@ -265,4 +280,5 @@ $cli
} catch (\Throwable $th) {
Console::error('Failed to check for a newer version' . "\n");
}
});
}
}

View file

@ -1,6 +1,6 @@
<?php
global $cli;
namespace Appwrite\Platform\Tasks;
use Appwrite\Auth\Auth;
use Appwrite\Docker\Compose;
@ -10,16 +10,29 @@ use Utopia\Analytics\GoogleAnalytics;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Validator\Text;
use Utopia\Platform\Action;
$cli
->task('install')
->desc('Install Appwrite')
->param('httpPort', '', new Text(4), 'Server HTTP port', true)
->param('httpsPort', '', new Text(4), 'Server HTTPS port', true)
->param('organization', 'appwrite', new Text(0), 'Docker Registry organization', true)
->param('image', 'appwrite', new Text(0), 'Main appwrite docker image', true)
->param('interactive', 'Y', new Text(1), 'Run an interactive session', true)
->action(function ($httpPort, $httpsPort, $organization, $image, $interactive) {
class Install extends Action
{
public static function getName(): string
{
return 'install';
}
public function __construct()
{
$this
->desc('Install Appwrite')
->param('httpPort', '', new Text(4), 'Server HTTP port', true)
->param('httpsPort', '', new Text(4), 'Server HTTPS port', true)
->param('organization', 'appwrite', new Text(0), 'Docker Registry organization', true)
->param('image', 'appwrite', new Text(0), 'Main appwrite docker image', true)
->param('interactive', 'Y', new Text(1), 'Run an interactive session', true)
->callback(fn ($httpPort, $httpsPort, $organization, $image, $interactive) => $this->action($httpPort, $httpsPort, $organization, $image, $interactive));
}
public function action(string $httpPort, string $httpsPort, string $organization, string $image, string $interactive): void
{
/**
* 1. Start - DONE
* 2. Check for older setup and get older version - DONE
@ -239,4 +252,5 @@ $cli
$analytics->createEvent('install/server', 'install', APP_VERSION_STABLE . ' - ' . $message);
Console::success($message);
}
});
}
}

View file

@ -1,20 +1,35 @@
<?php
global $cli;
namespace Appwrite\Platform\Tasks;
use Appwrite\Auth\Auth;
use Appwrite\Event\Certificate;
use Appwrite\Event\Delete;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\DateTime;
use Utopia\Database\Query;
use Utopia\Platform\Action;
$cli
->task('maintenance')
->desc('Schedules maintenance tasks and publishes them to resque')
->action(function () {
class Maintenance extends Action
{
public static function getName(): string
{
return 'maintenance';
}
public function __construct()
{
$this
->desc('Schedules maintenance tasks and publishes them to resque')
->inject('dbForConsole')
->callback(fn (Database $dbForConsole) => $this->action($dbForConsole));
}
public function action(Database $dbForConsole): void
{
Console::title('Maintenance V1');
Console::success(APP_NAME . ' maintenance process v1 has started');
@ -112,9 +127,7 @@ $cli
$usageStatsRetention1d = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_USAGE_1D', '8640000'); // 100 days
$cacheRetention = (int) App::getEnv('_APP_MAINTENANCE_RETENTION_CACHE', '2592000'); // 30 days
Console::loop(function () use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetention30m, $usageStatsRetention1d, $cacheRetention) {
$database = getConsoleDB();
Console::loop(function () use ($interval, $executionLogsRetention, $abuseLogsRetention, $auditLogRetention, $usageStatsRetention30m, $usageStatsRetention1d, $cacheRetention, $dbForConsole) {
$time = DateTime::now();
Console::info("[{$time}] Notifying workers with maintenance tasks every {$interval} seconds");
@ -124,9 +137,10 @@ $cli
notifyDeleteUsageStats($usageStatsRetention30m, $usageStatsRetention1d);
notifyDeleteConnections();
notifyDeleteExpiredSessions();
renewCertificates($database);
renewCertificates($dbForConsole);
notifyDeleteCache($cacheRetention);
// TODO: @Meldiron Every probably 24h, look for schedules with active=false, that doesnt have function anymore. Dlete such schedule
}, $interval);
});
}
}

View file

@ -1,19 +1,35 @@
<?php
global $cli, $register;
namespace Appwrite\Platform\Tasks;
use Utopia\Platform\Action;
use Utopia\CLI\Console;
use Appwrite\Migration\Migration;
use Utopia\App;
use Utopia\Cache\Cache;
use Utopia\Cache\Adapter\Redis as RedisCache;
use Utopia\Database\Validator\Authorization;
use Utopia\Registry\Registry;
use Utopia\Validator\Text;
$cli
->task('migrate')
->param('version', APP_VERSION_STABLE, new Text(32), 'Version to migrate to.', true)
->action(function ($version) use ($register) {
class Migrate extends Action
{
public static function getName(): string
{
return 'migrate';
}
public function __construct()
{
$this
->desc('Migrate Appwrite to new version')
->param('version', APP_VERSION_STABLE, new Text(8), 'Version to migrate to.', true)
->inject('register')
->callback(fn ($version, $register) => $this->action($version, $register));
}
public function action(string $version, Registry $register)
{
Authorization::disable();
if (!array_key_exists($version, Migration::$versions)) {
Console::error("Version {$version} not found.");
@ -87,4 +103,5 @@ $cli
Swoole\Event::wait(); // Wait for Coroutines to finish
$redis->flushAll();
Console::success('Data Migration Completed');
});
}
}

View file

@ -1,5 +1,8 @@
<?php
namespace Appwrite\Platform\Tasks;
use Utopia\Platform\Action;
use Utopia\Config\Config;
use Utopia\CLI\Console;
use Appwrite\Spec\Swagger2;
@ -19,10 +22,25 @@ use Appwrite\SDK\Language\Kotlin;
use Appwrite\SDK\Language\Android;
use Appwrite\SDK\Language\Swift;
use Appwrite\SDK\Language\SwiftClient;
use Exception;
use Throwable;
$cli
->task('sdks')
->action(function () {
class SDKs extends Action
{
public static function getName(): string
{
return 'sdks';
}
public function __construct()
{
$this
->desc('Generate Appwrite SDKs')
->callback(fn () => $this->action());
}
public function action(): void
{
$platforms = Config::getParam('platforms');
$selected = \strtolower(Console::confirm('Choose SDK ("*" for all):'));
$version = Console::confirm('Choose an Appwrite version');
@ -47,19 +65,19 @@ $cli
Console::info('Fetching API Spec for ' . $language['name'] . ' for ' . $platform['name'] . ' (version: ' . $version . ')');
$spec = file_get_contents(__DIR__ . '/../config/specs/swagger2-' . $version . '-' . $language['family'] . '.json');
$spec = file_get_contents(__DIR__ . '/../../../app/config/specs/swagger2-' . $version . '-' . $language['family'] . '.json');
$cover = 'https://appwrite.io/images/github.png';
$result = \realpath(__DIR__ . '/..') . '/sdks/' . $key . '-' . $language['key'];
$resultExamples = \realpath(__DIR__ . '/../..') . '/docs/examples/' . $version . '/' . $key . '-' . $language['key'];
$target = \realpath(__DIR__ . '/..') . '/sdks/git/' . $language['key'] . '/';
$readme = \realpath(__DIR__ . '/../../docs/sdks/' . $language['key'] . '/README.md');
$result = \realpath(__DIR__ . '/../../../app') . '/sdks/' . $key . '-' . $language['key'];
$resultExamples = \realpath(__DIR__ . '/../../..') . '/docs/examples/' . $version . '/' . $key . '-' . $language['key'];
$target = \realpath(__DIR__ . '/../../../app') . '/sdks/git/' . $language['key'] . '/';
$readme = \realpath(__DIR__ . '/../../../docs/sdks/' . $language['key'] . '/README.md');
$readme = ($readme) ? \file_get_contents($readme) : '';
$gettingStarted = \realpath(__DIR__ . '/../../docs/sdks/' . $language['key'] . '/GETTING_STARTED.md');
$gettingStarted = \realpath(__DIR__ . '/../../../docs/sdks/' . $language['key'] . '/GETTING_STARTED.md');
$gettingStarted = ($gettingStarted) ? \file_get_contents($gettingStarted) : '';
$examples = \realpath(__DIR__ . '/../../docs/sdks/' . $language['key'] . '/EXAMPLES.md');
$examples = \realpath(__DIR__ . '/../../../docs/sdks/' . $language['key'] . '/EXAMPLES.md');
$examples = ($examples) ? \file_get_contents($examples) : '';
$changelog = \realpath(__DIR__ . '/../../docs/sdks/' . $language['key'] . '/CHANGELOG.md');
$changelog = \realpath(__DIR__ . '/../../../docs/sdks/' . $language['key'] . '/CHANGELOG.md');
$changelog = ($changelog) ? \file_get_contents($changelog) : '# Change Log';
$warning = '**This SDK is compatible with Appwrite server version ' . $version . '. For older versions, please check [previous releases](' . $language['url'] . '/releases).**';
$license = 'BSD-3-Clause';
@ -255,4 +273,5 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
}
Console::exit();
});
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Appwrite\Platform\Tasks;
use Utopia\Platform\Action;
use Appwrite\Event\Certificate;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Database\Document;
use Utopia\Validator\Hostname;
class SSL extends Action
{
public static function getName(): string
{
return 'ssl';
}
public function __construct()
{
$this
->desc('Validate server certificates')
->param('domain', App::getEnv('_APP_DOMAIN', ''), new Hostname(), 'Domain to generate certificate for. If empty, main domain will be used.', true)
->callback(fn ($domain) => $this->action($domain));
}
public function action(string $domain): void
{
Console::success('Scheduling a job to issue a TLS certificate for domain: ' . $domain);
(new Certificate())
->setDomain(new Document([
'domain' => $domain
]))
->setSkipRenewCheck(true)
->trigger();
}
}

View file

@ -1,12 +1,14 @@
<?php
global $cli;
namespace Appwrite\Platform\Tasks;
use Utopia\Platform\Action;
use Utopia\Validator\Text;
use Appwrite\Specification\Format\OpenAPI3;
use Appwrite\Specification\Format\Swagger2;
use Appwrite\Specification\Specification;
use Appwrite\Utopia\Response;
use Exception;
use Swoole\Http\Response as HttpResponse;
use Utopia\App;
use Utopia\Cache\Adapter\None;
@ -15,14 +17,31 @@ use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Adapter\MySQL;
use Utopia\Database\Database;
use Utopia\Registry\Registry;
use Utopia\Request;
use Utopia\Validator\WhiteList;
$cli
->task('specs')
->param('version', 'latest', new Text(16), 'Spec version', true)
->param('mode', 'normal', new WhiteList(['normal', 'mocks']), 'Spec Mode', true)
->action(function ($version, $mode) use ($register) {
class Specs extends Action
{
public static function getName(): string
{
return 'specs';
}
public function __construct()
{
$this
->desc('Generate Appwrite API specifications')
->param('version', 'latest', new Text(16), 'Spec version', true)
->param('mode', 'normal', new WhiteList(['normal', 'mocks']), 'Spec Mode', true)
->inject('register')
->callback(fn (string $version, string $mode, Registry $register) => $this->action($version, $mode, $register));
}
public function action(string $version, string $mode, Registry $register): void
{
$db = $register->get('db');
$redis = $register->get('cache');
$appRoutes = App::getRoutes();
$response = new Response(new HttpResponse());
$mocks = ($mode === 'mocks');
@ -247,7 +266,7 @@ $cli
continue;
}
$path = __DIR__ . '/../config/specs/' . $format . '-' . $version . '-' . $platform . '.json';
$path = __DIR__ . '/../../../app/config/specs/' . $format . '-' . $version . '-' . $platform . '.json';
if (!file_put_contents($path, json_encode($specs->parse()))) {
throw new Exception('Failed to save spec file: ' . $path);
@ -256,4 +275,5 @@ $cli
Console::success('Saved spec file: ' . realpath($path));
}
}
});
}
}

View file

@ -0,0 +1,89 @@
<?php
namespace Appwrite\Platform\Tasks;
use Appwrite\Usage\Calculators\Aggregator;
use Appwrite\Usage\Calculators\Database;
use Appwrite\Usage\Calculators\TimeSeries;
use InfluxDB\Database as InfluxDatabase;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Database\Database as UtopiaDatabase;
use Utopia\Validator\WhiteList;
use Throwable;
use Utopia\Platform\Action;
class Usage extends Action
{
public static function getName(): string
{
return 'usage';
}
public function __construct()
{
$this
->desc('Schedules syncing data from influxdb to Appwrite console db')
->param('type', 'timeseries', new WhiteList(['timeseries', 'database']))
->inject('dbForConsole')
->inject('influxdb')
->inject('logError')
->callback(fn ($type, $dbForConsole, $influxDB, $logError) => $this->action($type, $dbForConsole, $influxDB, $logError));
}
protected function aggregateTimeseries(UtopiaDatabase $database, InfluxDatabase $influxDB, callable $logError): void
{
$interval = (int) App::getEnv('_APP_USAGE_TIMESERIES_INTERVAL', '30'); // 30 seconds (by default)
$usage = new TimeSeries($database, $influxDB, $logError);
Console::loop(function () use ($interval, $usage) {
$now = date('d-m-Y H:i:s', time());
Console::info("[{$now}] Aggregating Timeseries Usage data every {$interval} seconds");
$loopStart = microtime(true);
$usage->collect();
$loopTook = microtime(true) - $loopStart;
$now = date('d-m-Y H:i:s', time());
Console::info("[{$now}] Aggregation took {$loopTook} seconds");
}, $interval);
}
protected function aggregateDatabase(UtopiaDatabase $database, callable $logError): void
{
$interval = (int) App::getEnv('_APP_USAGE_DATABASE_INTERVAL', '900'); // 15 minutes (by default)
$usage = new Database($database, $logError);
$aggregrator = new Aggregator($database, $logError);
Console::loop(function () use ($interval, $usage, $aggregrator) {
$now = date('d-m-Y H:i:s', time());
Console::info("[{$now}] Aggregating database usage every {$interval} seconds.");
$loopStart = microtime(true);
$usage->collect();
$aggregrator->collect();
$loopTook = microtime(true) - $loopStart;
$now = date('d-m-Y H:i:s', time());
Console::info("[{$now}] Aggregation took {$loopTook} seconds");
}, $interval);
}
public function action(string $type, UtopiaDatabase $dbForConsole, InfluxDatabase $influxDB, callable $logError)
{
Console::title('Usage Aggregation V1');
Console::success(APP_NAME . ' usage aggregation process v1 has started');
$errorLogger = fn(Throwable $error, string $action = 'syncUsageStats') => $logError($error, "usage", $action);
switch ($type) {
case 'timeseries':
$this->aggregateTimeseries($dbForConsole, $influxDB, $errorLogger);
break;
case 'database':
$this->aggregateDatabase($dbForConsole, $errorLogger);
break;
default:
Console::error("Unsupported usage aggregation type");
}
}
}

View file

@ -1,15 +1,28 @@
<?php
global $cli;
namespace Appwrite\Platform\Tasks;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\CLI\Console;
use Utopia\Platform\Action;
$cli
->task('vars')
->desc('List all the server environment variables')
->action(function () {
class Vars extends Action
{
public static function getName(): string
{
return 'vars';
}
public function __construct()
{
$this
->desc('List all the server environment variables')
->callback(fn () => $this->action());
}
public function action(): void
{
$config = Config::getParam('variables', []);
$vars = [];
@ -22,4 +35,5 @@ $cli
foreach ($vars as $key => $value) {
Console::log('- ' . $value['name'] . '=' . App::getEnv($value['name'], ''));
}
});
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Appwrite\Platform\Tasks;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Platform\Action;
class Version extends Action
{
public static function getName(): string
{
return 'version';
}
public function __construct()
{
$this
->desc('Get the server version')
->callback(function () {
Console::log(App::getEnv('_APP_VERSION', 'UNKNOWN'));
});
}
}

View file

@ -1,19 +1,32 @@
<?php
global $cli;
namespace Appwrite\Platform\Tasks;
use Utopia\CLI\Console;
use Utopia\Database\DateTime;
use Utopia\Platform\Action;
use Utopia\Validator\Integer;
use Utopia\Validator\Text;
$cli
->task('volume-sync')
->desc('Runs rsync to sync certificates between the storage mount and traefik.')
->param('source', null, new Text(255), 'Source path to sync from.', false)
->param('destination', null, new Text(255), 'Destination path to sync to.', false)
->param('interval', null, new Integer(true), 'Interval to run rsync', false)
->action(function ($source, $destination, $interval) {
class VolumeSync extends Action
{
public static function getName(): string
{
return 'volume-sync';
}
public function __construct()
{
$this
->desc('Runs rsync to sync certificates between the storage mount and traefik.')
->param('source', null, new Text(255), 'Source path to sync from.', false)
->param('destination', null, new Text(255), 'Destination path to sync to.', false)
->param('interval', null, new Integer(true), 'Interval to run rsync', false)
->callback(fn ($source, $destination, $interval) => $this->action($source, $destination, $interval));
}
public function action(string $source, string $destination, int $interval)
{
Console::title('RSync V1');
Console::success(APP_NAME . ' rsync process v1 has started');
@ -42,4 +55,5 @@ $cli
Console::success($stdout);
Console::error($stderr);
}, $interval);
});
}
}

View file

@ -18,20 +18,29 @@ class Executor
public const METHOD_CONNECT = 'CONNECT';
public const METHOD_TRACE = 'TRACE';
private $endpoint;
private bool $selfSigned = false;
private $selfSigned = false;
private string $endpoint;
protected $headers = [
'content-type' => '',
];
protected array $headers;
protected int $cpus;
protected int $memory;
public function __construct(string $endpoint)
{
if (!filter_var($endpoint, FILTER_VALIDATE_URL)) {
throw new Exception('Unsupported endpoint');
}
$this->endpoint = $endpoint;
$this->cpus = \intval(App::getEnv('_APP_FUNCTIONS_CPUS', '1'));
$this->memory = intval(App::getEnv('_APP_FUNCTIONS_MEMORY', '512'));
$this->headers = [
'content-type' => 'application/json',
'authorization' => 'Bearer ' . App::getEnv('_APP_EXECUTOR_SECRET', ''),
];
}
/**
@ -42,45 +51,41 @@ class Executor
* @param string $deploymentId
* @param string $projectId
* @param string $source
* @param string $runtime
* @param string $baseImage
* @param string $image
* @param bool $remove
* @param string $entrypoint
* @param string $workdir
* @param string $destinaction
* @param string $network
* @param array $vars
* @param string $destination
* @param array $variables
* @param array $commands
*/
public function createRuntime(
string $deploymentId,
string $projectId,
string $source,
string $runtime,
string $baseImage,
string $image,
bool $remove = false,
string $entrypoint = '',
string $workdir = '',
string $destination = '',
array $vars = [],
array $variables = [],
array $commands = []
) {
$runtimeId = "$projectId-$deploymentId";
$route = "/runtimes";
$headers = [
'content-type' => 'application/json',
'x-appwrite-executor-key' => App::getEnv('_APP_EXECUTOR_SECRET', '')
];
$headers = [ 'x-opr-runtime-id' => $runtimeId ];
$params = [
'runtimeId' => "$projectId-$deploymentId",
'runtimeId' => $runtimeId,
'source' => $source,
'destination' => $destination,
'runtime' => $runtime,
'baseImage' => $baseImage,
'image' => $image,
'entrypoint' => $entrypoint,
'workdir' => $workdir,
'vars' => $vars,
'variables' => $variables,
'remove' => $remove,
'commands' => $commands
'commands' => $commands,
'cpus' => $this->cpus,
'memory' => $this->memory,
];
$timeout = (int) App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900);
@ -96,25 +101,48 @@ class Executor
}
/**
* Delete Runtime
*
* Deletes a runtime and cleans up any containers remaining.
* Create an execution
*
* @param string $projectId
* @param string $deploymentId
* @param string $payload
* @param array $variables
* @param int $timeout
* @param string $image
* @param string $source
* @param string $entrypoint
*
* @return array
*/
public function deleteRuntime(string $projectId, string $deploymentId)
{
public function createExecution(
string $projectId,
string $deploymentId,
string $payload,
array $variables,
int $timeout,
string $image,
string $source,
string $entrypoint,
) {
$runtimeId = "$projectId-$deploymentId";
$route = "/runtimes/$runtimeId";
$headers = [
'content-type' => 'application/json',
'x-appwrite-executor-key' => App::getEnv('_APP_EXECUTOR_SECRET', '')
$route = '/runtimes/' . $runtimeId . '/execution';
$headers = [ 'x-opr-runtime-id' => $runtimeId ];
$params = [
'runtimeId' => $runtimeId,
'variables' => $variables,
'payload' => $payload,
'timeout' => $timeout,
'image' => $image,
'source' => $source,
'entrypoint' => $entrypoint,
'cpus' => $this->cpus,
'memory' => $this->memory,
];
$params = [];
$timeout = (int) App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900);
$response = $this->call(self::METHOD_DELETE, $route, $headers, $params, true, 30);
$response = $this->call(self::METHOD_POST, $route, $headers, $params, true, $timeout);
$status = $response['headers']['status-code'];
if ($status >= 400) {
@ -124,93 +152,6 @@ class Executor
return $response['body'];
}
/**
* Create an execution
*
* @param string $projectId
* @param string $deploymentId
* @param string $path
* @param array $vars
* @param string $entrypoint
* @param string $data
* @param string runtime
* @param string $baseImage
* @param int $timeout
*
* @return array
*/
public function createExecution(
string $projectId,
string $deploymentId,
string $path,
array $vars,
string $entrypoint,
string $data,
string $runtime,
string $baseImage,
$timeout
) {
$route = "/execution";
$headers = [
'content-type' => 'application/json',
'x-appwrite-executor-key' => App::getEnv('_APP_EXECUTOR_SECRET', '')
];
$params = [
'runtimeId' => "$projectId-$deploymentId",
'vars' => $vars,
'data' => $data,
'timeout' => $timeout,
];
/* Add 2 seconds as a buffer to the actual timeout value since there can be a slight variance*/
$requestTimeout = $timeout + 2;
$response = $this->call(self::METHOD_POST, $route, $headers, $params, true, $requestTimeout);
$status = $response['headers']['status-code'];
for ($attempts = 0; $attempts < 10; $attempts++) {
try {
switch (true) {
case $status < 400:
return $response['body'];
case $status === 404:
$response = $this->createRuntime(
deploymentId: $deploymentId,
projectId: $projectId,
source: $path,
runtime: $runtime,
baseImage: $baseImage,
vars: $vars,
entrypoint: $entrypoint,
commands: []
);
$response = $this->call(self::METHOD_POST, $route, $headers, $params, true, $requestTimeout);
$status = $response['headers']['status-code'];
if ($status < 400) {
return $response['body'];
}
break;
case $status === 406:
$response = $this->call(self::METHOD_POST, $route, $headers, $params, true, $requestTimeout);
$status = $response['headers']['status-code'];
if ($status < 400) {
return $response['body'];
}
break;
default:
throw new \Exception($response['body']['message'], $status);
}
} catch (\Exception $e) {
throw new \Exception($e->getMessage(), $e->getCode());
}
sleep(2);
}
throw new Exception($response['body']['message'], 503);
}
/**
* Call
*

View file

@ -631,7 +631,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertStringContainsString('8.0', $execution['body']['response']);
$this->assertStringContainsString('êä', $execution['body']['response']); // tests unknown utf-8 chars
$this->assertEquals('', $execution['body']['stderr']);
$this->assertLessThan(0.500, $execution['body']['duration']);
$this->assertLessThan(1.500, $execution['body']['duration']);
/**
* Test for FAILURE
@ -750,7 +750,7 @@ class FunctionsCustomServerTest extends Scope
$this->assertStringContainsString('PHP', $execution['body']['response']);
$this->assertStringContainsString('8.0', $execution['body']['response']);
$this->assertStringContainsString('êä', $execution['body']['response']); // tests unknown utf-8 chars
$this->assertLessThan(0.500, $execution['body']['duration']);
$this->assertLessThan(1.500, $execution['body']['duration']);
return $data;
}
@ -1264,118 +1264,118 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(204, $response['headers']['status-code']);
}
public function testCreateCustomDartExecution()
{
$name = 'dart-2.15';
$folder = 'dart';
$code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
$this->packageCode($folder);
// public function testCreateCustomDartExecution()
// {
// $name = 'dart-2.15';
// $folder = 'dart';
// $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
// $this->packageCode($folder);
$entrypoint = 'main.dart';
$timeout = 2;
// $entrypoint = 'main.dart';
// $timeout = 2;
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'functionId' => ID::unique(),
'name' => 'Test ' . $name,
'runtime' => $name,
'events' => [],
'schedule' => '',
'timeout' => $timeout,
]);
// $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()), [
// 'functionId' => ID::unique(),
// 'name' => 'Test ' . $name,
// 'runtime' => $name,
// 'events' => [],
// 'schedule' => '',
// 'timeout' => $timeout,
// ]);
$functionId = $function['body']['$id'] ?? '';
// $functionId = $function['body']['$id'] ?? '';
$this->assertEquals(201, $function['headers']['status-code']);
// $this->assertEquals(201, $function['headers']['status-code']);
/** Create Variables */
$variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'key' => 'CUSTOM_VARIABLE',
'value' => 'variable',
]);
// /** Create Variables */
// $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()), [
// 'key' => 'CUSTOM_VARIABLE',
// 'value' => 'variable',
// ]);
$this->assertEquals(201, $variable['headers']['status-code']);
// $this->assertEquals(201, $variable['headers']['status-code']);
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
'content-type' => 'multipart/form-data',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'entrypoint' => $entrypoint,
'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
'activate' => true,
]);
// $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
// 'content-type' => 'multipart/form-data',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()), [
// 'entrypoint' => $entrypoint,
// 'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
// 'activate' => true,
// ]);
$deploymentId = $deployment['body']['$id'] ?? '';
$this->assertEquals(202, $deployment['headers']['status-code']);
// $deploymentId = $deployment['body']['$id'] ?? '';
// $this->assertEquals(202, $deployment['headers']['status-code']);
// Allow build step to run
sleep(80);
// // Allow build step to run
// sleep(80);
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => 'foobar',
'async' => true
]);
// $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()), [
// 'data' => 'foobar',
// 'async' => true
// ]);
$executionId = $execution['body']['$id'] ?? '';
// $executionId = $execution['body']['$id'] ?? '';
$this->assertEquals(202, $execution['headers']['status-code']);
// $this->assertEquals(202, $execution['headers']['status-code']);
$executionId = $execution['body']['$id'] ?? '';
// $executionId = $execution['body']['$id'] ?? '';
sleep(20);
// sleep(20);
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
// $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()));
$output = json_decode($executions['body']['response'], true);
// $output = json_decode($executions['body']['response'], true);
$this->assertEquals(200, $executions['headers']['status-code']);
$this->assertEquals('completed', $executions['body']['status']);
$this->assertEquals($functionId, $output['APPWRITE_FUNCTION_ID']);
$this->assertEquals('Test ' . $name, $output['APPWRITE_FUNCTION_NAME']);
$this->assertEquals($deploymentId, $output['APPWRITE_FUNCTION_DEPLOYMENT']);
$this->assertEquals('http', $output['APPWRITE_FUNCTION_TRIGGER']);
$this->assertEquals('Dart', $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
$this->assertEquals('2.15', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT']);
$this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT_DATA']);
$this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']);
$this->assertEquals('', $output['APPWRITE_FUNCTION_USER_ID']);
$this->assertEmpty($output['APPWRITE_FUNCTION_JWT']);
$this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']);
// $this->assertEquals(200, $executions['headers']['status-code']);
// $this->assertEquals('completed', $executions['body']['status']);
// $this->assertEquals($functionId, $output['APPWRITE_FUNCTION_ID']);
// $this->assertEquals('Test ' . $name, $output['APPWRITE_FUNCTION_NAME']);
// $this->assertEquals($deploymentId, $output['APPWRITE_FUNCTION_DEPLOYMENT']);
// $this->assertEquals('http', $output['APPWRITE_FUNCTION_TRIGGER']);
// $this->assertEquals('Dart', $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
// $this->assertEquals('2.15', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
// $this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT']);
// $this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT_DATA']);
// $this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']);
// $this->assertEquals('', $output['APPWRITE_FUNCTION_USER_ID']);
// $this->assertEmpty($output['APPWRITE_FUNCTION_JWT']);
// $this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']);
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
// $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()));
$this->assertEquals($executions['headers']['status-code'], 200);
$this->assertEquals($executions['body']['total'], 1);
$this->assertIsArray($executions['body']['executions']);
$this->assertCount(1, $executions['body']['executions']);
$this->assertEquals($executions['body']['executions'][0]['$id'], $executionId);
$this->assertEquals($executions['body']['executions'][0]['trigger'], 'http');
$this->assertStringContainsString('foobar', $executions['body']['executions'][0]['response']);
// $this->assertEquals($executions['headers']['status-code'], 200);
// $this->assertEquals($executions['body']['total'], 1);
// $this->assertIsArray($executions['body']['executions']);
// $this->assertCount(1, $executions['body']['executions']);
// $this->assertEquals($executions['body']['executions'][0]['$id'], $executionId);
// $this->assertEquals($executions['body']['executions'][0]['trigger'], 'http');
// $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['response']);
// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], []);
// // Cleanup : Delete function
// $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// 'x-appwrite-key' => $this->getProject()['apiKey'],
// ], []);
$this->assertEquals(204, $response['headers']['status-code']);
}
// $this->assertEquals(204, $response['headers']['status-code']);
// }
#[Retry(count: 1)]
public function testCreateCustomRubyExecution()

View file

@ -313,7 +313,7 @@ class ProjectsConsoleClientTest extends Scope
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/usage', array_merge([
$response = $this->client->call(Client::METHOD_GET, '/project/usage', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));