1
0
Fork 0
mirror of synced 2024-06-29 19:50:26 +12:00

Merge branch 'feat-functions-refactor' of github.com:appwrite/appwrite into feat-add-builds-worker

This commit is contained in:
Christy Jacob 2022-01-25 21:11:29 +04:00
commit e2bc4e748f
23 changed files with 1931 additions and 1710 deletions

View file

@ -1,6 +1,8 @@
# Unreleased Version 0.13.0 # Unreleased Version 0.13.0
- Added ability to create syncronous function executions - Added ability to create syncronous function executions
- Introduced new execution model for functions - Introduced new execution model for functions
- Improved functions execution times
- Improved functions execution times
# Version 0.12.1 # Version 0.12.1
## Bugs ## Bugs

View file

@ -271,7 +271,8 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/worker-functions && \ chmod +x /usr/local/bin/worker-functions && \
chmod +x /usr/local/bin/worker-builds && \ chmod +x /usr/local/bin/worker-builds && \
chmod +x /usr/local/bin/worker-mails && \ chmod +x /usr/local/bin/worker-mails && \
chmod +x /usr/local/bin/worker-webhooks chmod +x /usr/local/bin/worker-webhooks && \
chmod +x /usr/local/bin/executor
# Letsencrypt Permissions # Letsencrypt Permissions
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/ RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/

View file

@ -2015,7 +2015,7 @@ $collections = [
'filters' => [], 'filters' => [],
], ],
[ [
'$id' => 'automaticDeploy', '$id' => 'deploy',
'type' => Database::VAR_BOOLEAN, 'type' => Database::VAR_BOOLEAN,
'format' => '', 'format' => '',
'size' => 0, 'size' => 0,
@ -2116,7 +2116,7 @@ $collections = [
'filters' => [], 'filters' => [],
], ],
[ [
'$id' => 'buildTime', '$id' => 'time',
'type' => Database::VAR_INTEGER, 'type' => Database::VAR_INTEGER,
'format' => '', 'format' => '',
'size' => 0, 'size' => 0,
@ -2127,7 +2127,7 @@ $collections = [
'filters' => [], 'filters' => [],
], ],
[ [
'$id' => 'envVars', '$id' => 'vars',
'type' => Database::VAR_STRING, 'type' => Database::VAR_STRING,
'format' => '', 'format' => '',
'size' => 16384, 'size' => 16384,
@ -2159,6 +2159,17 @@ $collections = [
'array' => false, 'array' => false,
'filters' => [], 'filters' => [],
], ],
[
'$id' => 'search',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
], ],
'indexes' => [ 'indexes' => [
[ [
@ -2168,6 +2179,13 @@ $collections = [
'lengths' => [Database::LENGTH_KEY], 'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC], 'orders' => [Database::ORDER_ASC],
], ],
[
'$id' => '_key_search',
'type' => Database::INDEX_FULLTEXT,
'attributes' => ['search'],
'lengths' => [2048],
'orders' => [Database::ORDER_ASC],
],
], ],
], ],

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

@ -433,7 +433,7 @@ App::delete('/v1/functions/:functionId')
// Request executor to delete tag containers // Request executor to delete tag containers
$ch = \curl_init(); $ch = \curl_init();
\curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor:8080/v1/cleanup/function"); \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/cleanup/function");
\curl_setopt($ch, CURLOPT_POST, true); \curl_setopt($ch, CURLOPT_POST, true);
\curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ \curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'functionId' => $functionId 'functionId' => $functionId
@ -496,14 +496,14 @@ App::post('/v1/functions/:functionId/tags')
->param('functionId', '', new UID(), 'Function ID.') ->param('functionId', '', new UID(), 'Function ID.')
->param('entrypoint', '', new Text('1028'), 'Entrypoint File.') ->param('entrypoint', '', new Text('1028'), 'Entrypoint File.')
->param('code', [], new File(), 'Gzip file with your code package. When used with the Appwrite CLI, pass the path to your code directory, and the CLI will automatically package your code. Use a path that is within the current directory.', false) ->param('code', [], new File(), 'Gzip file with your code package. When used with the Appwrite CLI, pass the path to your code directory, and the CLI will automatically package your code. Use a path that is within the current directory.', false)
->param('automaticDeploy', false, new Boolean(true), 'Automatically deploy the function when it is finished building.', false) ->param('deploy', false, new Boolean(true), 'Automatically deploy the function when it is finished building.', false)
->inject('request') ->inject('request')
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->inject('usage') ->inject('usage')
->inject('user') ->inject('user')
->inject('project') ->inject('project')
->action(function ($functionId, $entrypoint, $file, $automaticDeploy, $request, $response, $dbForProject, $usage, $user, $project) { ->action(function ($functionId, $entrypoint, $file, $deploy, $request, $response, $dbForProject, $usage, $user, $project) {
/** @var Utopia\Swoole\Request $request */ /** @var Utopia\Swoole\Request $request */
/** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */ /** @var Utopia\Database\Database $dbForProject */
@ -552,15 +552,15 @@ App::post('/v1/functions/:functionId/tags')
throw new Exception('Failed moving file', 500); throw new Exception('Failed moving file', 500);
} }
if ((bool) $automaticDeploy) { if ((bool) $deploy) {
// Remove automaticDeploy for all other tags. // Remove deploy for all other tags.
$tags = $dbForProject->find('tags', [ $tags = $dbForProject->find('tags', [
new Query('automaticDeploy', Query::TYPE_EQUAL, [true]), new Query('deploy', Query::TYPE_EQUAL, [true]),
new Query('functionId', Query::TYPE_EQUAL, [$functionId]) new Query('functionId', Query::TYPE_EQUAL, [$functionId])
]); ]);
foreach ($tags as $tag) { foreach ($tags as $tag) {
$tag->setAttribute('automaticDeploy', false); $tag->setAttribute('deploy', false);
$dbForProject->updateDocument('tags', $tag->getId(), $tag); $dbForProject->updateDocument('tags', $tag->getId(), $tag);
} }
} }
@ -581,7 +581,7 @@ App::post('/v1/functions/:functionId/tags')
'status' => 'processing', 'status' => 'processing',
'buildStdout' => '', 'buildStdout' => '',
'buildStderr' => '', 'buildStderr' => '',
'automaticDeploy' => ($automaticDeploy === 'true'), 'deploy' => ($deploy === 'true'),
])); ]));
$usage $usage
@ -743,7 +743,7 @@ App::delete('/v1/functions/:functionId/tags/:tagId')
// Request executor to delete tag containers // Request executor to delete tag containers
$ch = \curl_init(); $ch = \curl_init();
\curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor:8080/v1/cleanup/tag"); \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/cleanup/tag");
\curl_setopt($ch, CURLOPT_POST, true); \curl_setopt($ch, CURLOPT_POST, true);
\curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ \curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'tagId' => $tagId 'tagId' => $tagId
@ -901,7 +901,7 @@ App::post('/v1/functions/:functionId/executions')
// Directly execute function. // Directly execute function.
$ch = \curl_init(); $ch = \curl_init();
\curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor:8080/v1/execute"); \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/execute");
\curl_setopt($ch, CURLOPT_POST, true); \curl_setopt($ch, CURLOPT_POST, true);
\curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ \curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'trigger' => 'http', 'trigger' => 'http',
@ -1029,50 +1029,47 @@ App::get('/v1/functions/:functionId/executions/:executionId')
}); });
App::get('/v1/builds') App::get('/v1/builds')
->groups(['api', 'functions']) ->groups(['api', 'functions'])
->desc('Get Builds') ->desc('List Builds')
->label('scope', 'execution.read') ->label('scope', 'functions.read')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions') ->label('sdk.namespace', 'functions')
->label('sdk.method', 'listBuilds') ->label('sdk.method', 'builds')
->label('sdk.description', '/docs/references/functions/list-builds.md') ->label('sdk.description', '/docs/references/functions/list-builds.md')
->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_BUILD_LIST) ->label('sdk.response.model', Response::MODEL_BUILD_LIST)
->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('limit', 25, new Range(0, 100), 'Maximum number of builds to return in response. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true) ->param('offset', 0, new Range(0, APP_LIMIT_COUNT), 'Offset value. The default value is 0. Use this value to manage pagination. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursor', '', new UID(), 'ID of the build used as the starting point for the query, excluding the build itself. Should be used for efficient pagination when working with large sets of data.', true) ->param('cursor', '', new UID(), 'ID of the function used as the starting point for the query, excluding the function itself. Should be used for efficient pagination when working with large sets of data. [learn more about pagination](https://appwrite.io/docs/pagination)', true)
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true) ->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
->inject('response') ->param('orderType', 'ASC', new WhiteList(['ASC', 'DESC'], true), 'Order result by ASC or DESC order.', true)
->inject('dbForProject') ->inject('response')
->action(function ($limit, $offset, $search, $cursor, $cursorDirection, $response, $dbForProject) { ->inject('dbForProject')
/** @var Appwrite\Utopia\Response $response */ ->action(function ($search, $limit, $offset, $cursor, $cursorDirection, $orderType, $response, $dbForProject) {
/** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Database $dbForProject */
if (!empty($cursor)) { if (!empty($cursor)) {
$cursorExecution = $dbForProject->getDocument('builds', $cursor); $cursorFunction = $dbForProject->getDocument('builds', $cursor);
if ($cursorExecution->isEmpty()) { if ($cursorFunction->isEmpty()) {
throw new Exception("Execution '{$cursor}' for the 'cursor' value not found.", 400); throw new Exception("Build '{$cursor}' for the 'cursor' value not found.", 400);
}
} }
}
$queries = []; $queries = [];
if (!empty($search)) { if (!empty($search)) {
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]); $queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
} }
$results = $dbForProject->find('builds', $queries, $limit, $offset, [], [Database::ORDER_DESC], $cursorExecution ?? null, $cursorDirection); $response->dynamic(new Document([
'builds' => $dbForProject->find('builds', $queries, $limit, $offset, [], [$orderType], $cursorFunction ?? null, $cursorDirection),
$sum = $dbForProject->count('builds', $queries, APP_LIMIT_COUNT); 'sum' => $dbForProject->count('builds', $queries, APP_LIMIT_COUNT),
]), Response::MODEL_BUILD_LIST);
$response->dynamic(new Document([ });
'builds' => $results,
'sum' => $sum,
]), Response::MODEL_BUILD_LIST);
});
App::get('/v1/builds/:buildId') App::get('/v1/builds/:buildId')
->groups(['api', 'functions']) ->groups(['api', 'functions'])

View file

@ -1,40 +1,40 @@
<?php <?php
require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../vendor/autoload.php';
use Utopia\Database\Document;
use Utopia\Database\Database;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Appwrite\Event\Event; use Appwrite\Event\Event;
use Appwrite\Utopia\Response\Model\Execution;
use Appwrite\Messaging\Adapter\Realtime; use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Stats\Stats; use Appwrite\Stats\Stats;
use Utopia\App;
use Utopia\Swoole\Request;
use Appwrite\Utopia\Response; use Appwrite\Utopia\Response;
use Utopia\CLI\Console; use Appwrite\Utopia\Response\Model\Execution;
use Swoole\Process; use Cron\CronExpression;
use Swoole\Http\Server; use Swoole\ConnectionPool;
use Swoole\Coroutine as Co;
use Swoole\Http\Request as SwooleRequest; use Swoole\Http\Request as SwooleRequest;
use Swoole\Http\Response as SwooleResponse; use Swoole\Http\Response as SwooleResponse;
use Utopia\Orchestration\Orchestration; use Swoole\Http\Server;
use Utopia\Database\Adapter\MariaDB; use Swoole\Process;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Cache\Adapter\Redis as RedisCache; use Utopia\Cache\Adapter\Redis as RedisCache;
use Utopia\Cache\Cache;
use Utopia\Config\Config; use Utopia\Config\Config;
use Utopia\Database\Adapter\MariaDB;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Logger\Log;
use Utopia\Orchestration\Adapter\DockerAPI;
use Utopia\Orchestration\Adapter\DockerCLI;
use Utopia\Orchestration\Orchestration;
use Utopia\Registry\Registry;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
use Utopia\Swoole\Request;
use Utopia\Validator\ArrayList; use Utopia\Validator\ArrayList;
use Utopia\Validator\JSON; use Utopia\Validator\JSON;
use Utopia\Validator\Text; use Utopia\Validator\Text;
use Cron\CronExpression;
use Swoole\ConnectionPool;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
use Swoole\Coroutine as Co;
use Utopia\Cache\Cache;
use Utopia\Database\Query;
use Utopia\Orchestration\Adapter\DockerCLI;
use Utopia\Logger\Log;
use Utopia\Orchestration\Adapter\DockerAPI;
use Utopia\Registry\Registry;
require_once __DIR__ . '/init.php'; require_once __DIR__ . '/init.php';
@ -91,6 +91,7 @@ $orchestrationPool = new ConnectionPool(function () {
return $orchestration; return $orchestration;
}, 6); }, 6);
try { try {
$runtimes = Config::getParam('runtimes'); $runtimes = Config::getParam('runtimes');
@ -98,19 +99,22 @@ try {
Co\run(function () use ($runtimes, $orchestrationPool) { Co\run(function () use ($runtimes, $orchestrationPool) {
foreach ($runtimes as $runtime) { foreach ($runtimes as $runtime) {
go(function () use ($runtime, $orchestrationPool) { go(function () use ($runtime, $orchestrationPool) {
$orchestration = $orchestrationPool->get(); try {
$orchestration = $orchestrationPool->get();
Console::info('Warming up ' . $runtime['name'] . ' ' . $runtime['version'] . ' environment...'); Console::info('Warming up ' . $runtime['name'] . ' ' . $runtime['version'] . ' environment...');
$response = $orchestration->pull($runtime['image']); $response = $orchestration->pull($runtime['image']);
if ($response) { if ($response) {
Console::success("Successfully Warmed up {$runtime['name']} {$runtime['version']}!"); Console::success("Successfully Warmed up {$runtime['name']} {$runtime['version']}!");
} else { } else {
Console::warning("Failed to Warmup {$runtime['name']} {$runtime['version']}!"); Console::warning("Failed to Warmup {$runtime['name']} {$runtime['version']}!");
}
} catch (\Throwable $th) {
} finally {
$orchestrationPool->put($orchestration);
} }
$orchestrationPool->put($orchestration);
}); });
} }
}); });
@ -123,11 +127,15 @@ try {
$activeFunctions->create(); $activeFunctions->create();
Co\run(function () use ($orchestrationPool, $activeFunctions) { Co\run(function () use ($orchestrationPool, $activeFunctions) {
$orchestration = $orchestrationPool->get(); try {
$executionStart = \microtime(true); $orchestration = $orchestrationPool->get();
$executionStart = \microtime(true);
$residueList = $orchestration->list(['label' => 'appwrite-type=function']);
} catch (\Throwable $th) {
} finally {
$orchestrationPool->put($orchestration);
}
$residueList = $orchestration->list(['label' => 'appwrite-type=function']);
$orchestrationPool->put($orchestration);
foreach ($residueList as $value) { foreach ($residueList as $value) {
go(fn () => $activeFunctions->set($value->getName(), [ go(fn () => $activeFunctions->set($value->getName(), [
@ -195,7 +203,7 @@ function createRuntimeServer(string $functionId, string $projectId, string $tagI
'INTERNAL_RUNTIME_KEY' => $secret 'INTERNAL_RUNTIME_KEY' => $secret
]); ]);
$vars = \array_merge($vars, $build->getAttribute('envVars', [])); // for gettng endpoint. $vars = \array_merge($vars, $build->getAttribute('vars', [])); // for gettng endpoint.
$container = 'appwrite-function-' . $tag->getId(); $container = 'appwrite-function-' . $tag->getId();
@ -234,7 +242,9 @@ function createRuntimeServer(string $functionId, string $projectId, string $tagI
$device = Storage::getDevice('builds'); $device = Storage::getDevice('builds');
if (!\file_exists($tagPathTargetDir)) { if (!\file_exists($tagPathTargetDir)) {
if (!\mkdir($tagPathTargetDir, 0777, true)) { if (@\mkdir($tagPathTargetDir, 0777, true)) {
\chmod($tagPathTargetDir, 0777);
} else {
throw new Exception('Can\'t create directory ' . $tagPathTargetDir); throw new Exception('Can\'t create directory ' . $tagPathTargetDir);
} }
} }
@ -268,9 +278,7 @@ function createRuntimeServer(string $functionId, string $projectId, string $tagI
->setMemory(App::getEnv('_APP_FUNCTIONS_MEMORY', '256')) ->setMemory(App::getEnv('_APP_FUNCTIONS_MEMORY', '256'))
->setSwap(App::getEnv('_APP_FUNCTIONS_MEMORY_SWAP', '256')); ->setSwap(App::getEnv('_APP_FUNCTIONS_MEMORY_SWAP', '256'));
foreach ($vars as $key => $value) { $vars = array_map(fn ($v) => strval($v), $vars);
$vars[$key] = strval($value);
}
// Launch runtime server // Launch runtime server
$id = $orchestration->run( $id = $orchestration->run(
@ -312,9 +320,8 @@ function createRuntimeServer(string $functionId, string $projectId, string $tagI
var_dump($th->getTraceAsString()); var_dump($th->getTraceAsString());
$orchestrationPool->put($orchestration ?? null); $orchestrationPool->put($orchestration ?? null);
throw $th; throw $th;
} finally {
$orchestrationPool->put($orchestration);
} }
$orchestrationPool->put($orchestration);
}; };
function execute(string $trigger, string $projectId, string $executionId, string $functionId, Database $database, string $event = '', string $eventData = '', string $data = '', array $webhooks = [], string $userId = '', string $jwt = ''): array function execute(string $trigger, string $projectId, string $executionId, string $functionId, Database $database, string $event = '', string $eventData = '', string $data = '', array $webhooks = [], string $userId = '', string $jwt = ''): array
@ -394,7 +401,7 @@ function execute(string $trigger, string $projectId, string $executionId, string
'APPWRITE_FUNCTION_PROJECT_ID' => $projectId, 'APPWRITE_FUNCTION_PROJECT_ID' => $projectId,
]); ]);
$vars = \array_merge($vars, $build->getAttribute('envVars', [])); $vars = \array_merge($vars, $build->getAttribute('vars', []));
$container = 'appwrite-function-' . $tag->getId(); $container = 'appwrite-function-' . $tag->getId();
@ -405,7 +412,7 @@ function execute(string $trigger, string $projectId, string $executionId, string
$database->createDocument('builds', new Document([ $database->createDocument('builds', new Document([
'$id' => $buildId, '$id' => $buildId,
'$read' => ($userId !== '') ? ['user:' . $userId] : [], '$read' => ($userId !== '') ? ['user:' . $userId] : [],
'$write' => ['role:all'], '$write' => [],
'dateCreated' => time(), 'dateCreated' => time(),
'status' => 'processing', 'status' => 'processing',
'outputPath' => '', 'outputPath' => '',
@ -414,8 +421,8 @@ function execute(string $trigger, string $projectId, string $executionId, string
'sourceType' => Storage::DEVICE_LOCAL, 'sourceType' => Storage::DEVICE_LOCAL,
'stdout' => '', 'stdout' => '',
'stderr' => '', 'stderr' => '',
'buildTime' => 0, 'time' => 0,
'envVars' => [ 'vars' => [
'ENTRYPOINT_NAME' => $tag->getAttribute('entrypoint'), 'ENTRYPOINT_NAME' => $tag->getAttribute('entrypoint'),
'APPWRITE_FUNCTION_ID' => $function->getId(), 'APPWRITE_FUNCTION_ID' => $function->getId(),
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name', ''), 'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name', ''),
@ -444,7 +451,7 @@ function execute(string $trigger, string $projectId, string $executionId, string
} }
try { try {
if (!$activeFunctions->exists($container)) { // Create contianer if not ready if (!$activeFunctions->exists($container)) { // Create container if not ready
createRuntimeServer($functionId, $projectId, $tag->getId(), $database); createRuntimeServer($functionId, $projectId, $tag->getId(), $database);
} else if ($activeFunctions->get($container)['status'] === 'Down') { } else if ($activeFunctions->get($container)['status'] === 'Down') {
sleep(1); sleep(1);
@ -490,7 +497,7 @@ function execute(string $trigger, string $projectId, string $executionId, string
'APPWRITE_FUNCTION_PROJECT_ID' => $projectId 'APPWRITE_FUNCTION_PROJECT_ID' => $projectId
]); ]);
$vars = \array_merge($vars, $build->getAttribute('envVars', [])); $vars = \array_merge($vars, $build->getAttribute('vars', []));
$stdout = ''; $stdout = '';
$stderr = ''; $stderr = '';
@ -512,7 +519,7 @@ function execute(string $trigger, string $projectId, string $executionId, string
$body = \json_encode([ $body = \json_encode([
'path' => '/usr/code', 'path' => '/usr/code',
'file' => $build->getAttribute('envVars', [])['ENTRYPOINT_NAME'], 'file' => $build->getAttribute('vars', [])['ENTRYPOINT_NAME'],
'env' => $vars, 'env' => $vars,
'payload' => $data, 'payload' => $data,
'timeout' => $function->getAttribute('timeout', (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)) 'timeout' => $function->getAttribute('timeout', (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900))
@ -652,6 +659,261 @@ function execute(string $trigger, string $projectId, string $executionId, string
]; ];
}; };
function runBuildStage(string $buildId, string $projectID): Document
{
global $runtimes;
global $orchestrationPool;
global $register;
/** @var Orchestration $orchestration */
$orchestration = $orchestrationPool->get();
$buildStdout = '';
$buildStderr = '';
$db = $register->get('dbPool')->get();
$redis = $register->get('redisPool')->get();
$cache = new Cache(new RedisCache($redis));
$database = new Database(new MariaDB($db), $cache);
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$database->setNamespace('_project_' . $projectID);
// Check if build has already been run
$build = $database->getDocument('builds', $buildId);
try {
// If we already have a built package ready there is no need to rebuild.
if ($build->getAttribute('status') === 'ready' && \file_exists($build->getAttribute('outputPath'))) {
return $build;
}
// Update Tag Status
$build->setAttribute('status', 'building');
$database->updateDocument('builds', $build->getId(), $build);
// Check if runtime is active
$runtime = $runtimes[$build->getAttribute('runtime', '')] ?? null;
if (\is_null($runtime)) {
throw new Exception('Runtime "' . $build->getAttribute('runtime', '') . '" is not supported');
}
// Grab Tag Files
$tagPath = $build->getAttribute('source', '');
$sourceType = $build->getAttribute('sourceType', '');
$device = Storage::getDevice('builds');
$tagPathTarget = '/tmp/project-' . $projectID . '/' . $build->getId() . '/code.tar.gz';
$tagPathTargetDir = \pathinfo($tagPathTarget, PATHINFO_DIRNAME);
$container = 'build-stage-' . $build->getId();
// Perform various checks
if (!\file_exists($tagPathTargetDir)) {
if (@\mkdir($tagPathTargetDir, 0777, true)) {
\chmod($tagPathTargetDir, 0777);
} else {
throw new Exception('Can\'t create directory ' . $tagPathTargetDir);
}
}
if (!\file_exists($tagPathTarget)) {
if (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) === Storage::DEVICE_LOCAL) {
if (!\copy($tagPath, $tagPathTarget)) {
throw new Exception('Can\'t create temporary code file ' . $tagPathTarget);
}
} else {
$buffer = $device->read($tagPath);
\file_put_contents($tagPathTarget, $buffer);
}
}
if (!$device->exists($tagPath)) {
throw new Exception('Code is not readable: ' . $build->getAttribute('source', ''));
}
$vars = $build->getAttribute('vars', []);
// Start tracking time
$buildStart = \microtime(true);
$time = \time();
$orchestration
->setCpus(App::getEnv('_APP_FUNCTIONS_CPUS', 0))
->setMemory(App::getEnv('_APP_FUNCTIONS_MEMORY', 256))
->setSwap(App::getEnv('_APP_FUNCTIONS_MEMORY_SWAP', 256));
$vars = array_map(fn ($v) => strval($v), $vars);
$path = '/tmp/project-' . $projectID . '/' . $build->getId() . '/builtCode';
if (!\file_exists($path)) {
if (@\mkdir($path, 0777, true)) {
\chmod($path, 0777);
} else {
throw new Exception('Can\'t create directory /tmp/project-' . $projectID . '/' . $build->getId() . '/builtCode');
}
}
// Launch build container
$id = $orchestration->run(
image: $runtime['base'],
name: $container,
vars: $vars,
workdir: '/usr/code',
labels: [
'appwrite-type' => 'function',
'appwrite-created' => strval($time),
'appwrite-runtime' => $build->getAttribute('runtime', ''),
'appwrite-project' => $projectID,
'appwrite-build' => $build->getId(),
],
command: [
'tail',
'-f',
'/dev/null'
],
hostname: $container,
mountFolder: $tagPathTargetDir,
volumes: [
'/tmp/project-' . $projectID . '/' . $build->getId() . '/builtCode' . ':/usr/builtCode:rw'
]
);
if (empty($id)) {
throw new Exception('Failed to start build container');
}
// Extract user code into build container
$untarStdout = '';
$untarStderr = '';
$untarSuccess = $orchestration->execute(
name: $container,
command: [
'sh',
'-c',
'mkdir -p /usr/code && cp /tmp/code.tar.gz /usr/workspace/code.tar.gz && cd /usr/workspace/ && tar -zxf /usr/workspace/code.tar.gz -C /usr/code && rm /usr/workspace/code.tar.gz'
],
stdout: $untarStdout,
stderr: $untarStderr,
timeout: 60
);
if (!$untarSuccess) {
throw new Exception('Failed to extract tar: ' . $untarStderr);
}
// Build Code / Install Dependencies
$buildSuccess = $orchestration->execute(
name: $container,
command: ['sh', '-c', 'cd /usr/local/src && ./build.sh'],
stdout: $buildStdout,
stderr: $buildStderr,
timeout: App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900)
);
if (!$buildSuccess) {
throw new Exception('Failed to build dependencies: ' . $buildStderr);
}
// Repackage Code and Save.
$compressStdout = '';
$compressStderr = '';
$builtCodePath = '/tmp/project-' . $projectID . '/' . $build->getId() . '/builtCode/code.tar.gz';
$compressSuccess = $orchestration->execute(
name: $container,
command: [
'tar', '-C', '/usr/code', '-czvf', '/usr/builtCode/code.tar.gz', './'
],
stdout: $compressStdout,
stderr: $compressStderr,
timeout: 60
);
if (!$compressSuccess) {
throw new Exception('Failed to compress built code: ' . $compressStderr);
}
// Remove Container
$orchestration->remove($id, true);
// Check if the build was successful by checking if file exists
if (!\file_exists($builtCodePath)) {
throw new Exception('Something went wrong during the build process.');
}
// Upload new code
$device = Storage::getDevice('builds');
$path = $device->getPath(\uniqid() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
if (!\file_exists(\dirname($path))) { // Checks if directory path to file exists
if (@\mkdir(\dirname($path), 0777, true)) {
\chmod(\dirname($path), 0777);
} else {
throw new Exception('Can\'t create directory: ' . \dirname($path));
}
}
if (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) === Storage::DEVICE_LOCAL) {
if (!$device->move($builtCodePath, $path)) {
throw new Exception('Failed to upload built code upload to storage', 500);
}
} else {
if (!$device->upload($builtCodePath, $path)) {
throw new Exception('Failed to upload built code upload to storage', 500);
}
}
if ($buildStdout == '') {
$buildStdout = 'Build Successful!';
}
$build
->setAttribute('outputPath', $path)
->setAttribute('status', 'ready')
->setAttribute('stdout', \utf8_encode(\mb_substr($buildStdout, -4096)))
->setAttribute('stderr', \utf8_encode(\mb_substr($buildStderr, -4096)))
->setAttribute('time', $time);
// Update build with built code attribute
$build = $database->updateDocument('builds', $buildId, $build);
$buildEnd = \microtime(true);
Console::info('Build Stage Ran in ' . ($buildEnd - $buildStart) . ' seconds');
} catch (Exception $e) {
$build
->setAttribute('status', 'failed')
->setAttribute('stdout', \utf8_encode(\mb_substr($buildStdout, -4096)))
->setAttribute('stderr', \utf8_encode(\mb_substr($e->getMessage(), -4096)));
$build = $database->updateDocument('builds', $buildId, $build);
// also remove the container if it exists
if (isset($id)) {
$orchestration->remove($id, true);
}
$register->get('dbPool')->put($db);
$register->get('redisPool')->put($redis);
throw new Exception('Build failed: ' . $e->getMessage());
}
$orchestrationPool->put($orchestration);
$register->get('dbPool')->put($db);
$register->get('redisPool')->put($redis);
return $build;
}
App::post('/v1/execute') // Define Route App::post('/v1/execute') // Define Route
->desc('Execute a function') ->desc('Execute a function')
->param('trigger', '', new Text(1024)) ->param('trigger', '', new Text(1024))
@ -683,7 +945,6 @@ App::post('/v1/execute') // Define Route
} }
); );
// Cleanup Endpoints used internally by appwrite when a function or tag gets deleted to also clean up their containers // Cleanup Endpoints used internally by appwrite when a function or tag gets deleted to also clean up their containers
App::post('/v1/cleanup/function') App::post('/v1/cleanup/function')
->param('functionId', '', new UID()) ->param('functionId', '', new UID())
@ -788,27 +1049,125 @@ App::post('/v1/cleanup/tag')
return $response->json(['success' => true]); return $response->json(['success' => true]);
}); });
App::post('/v1/tag')
App::post('/v1/executor/runtime')
->desc('Create a new runtime server')
->param('functionId', '', new UID(), 'Function unique ID.') ->param('functionId', '', new UID(), 'Function unique ID.')
->param('tagId', '', new UID(), 'Tag unique ID.') ->param('tagId', '', new UID(), 'Tag unique ID.')
->inject('projectID') ->param('userId', '', new UID(), 'User unique ID.', true)
->inject('response') ->inject('response')
->inject('dbForProject') ->inject('dbForProject')
->action(function (string $functionId, string $tagId, string $projectID, Response $response, Database $dbForProject) { ->inject('projectID')
try { ->inject('register')
Console::success('Creating runtime for tag ' . $tagId); ->action(function (string $functionId, string $tagId, string $userId, Response $response, Database $dbForProject, string $projectID, Registry $register) use ($runtimes) {
createRuntimeServer($functionId, $projectID, $tagId, $dbForProject); // Get function document
} catch (\Throwable $th) { $function = $dbForProject->getDocument('functions', $functionId);
$response // Get tag document
->setStatusCode(400) $tag = $dbForProject->getDocument('tags', $tagId);
->json(['error' => $th->getMessage()]);
};
$response // Check if both documents exist
->setStatusCode(201) if ($function->isEmpty()) {
->noContent(); throw new Exception('Function not found', 404);
}
if ($tag->isEmpty()) {
throw new Exception('Tag not found', 404);
}
$runtime = $runtimes[$function->getAttribute('runtime')] ?? null;
if (\is_null($runtime)) {
throw new Exception('Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
}
// Create a new build entry
$buildId = $dbForProject->getId();
if ($tag->getAttribute('buildId')) {
$buildId = $tag->getAttribute('buildId');
} else {
try {
$dbForProject->createDocument('builds', new Document([
'$id' => $buildId,
'$read' => (!empty($userId)) ? ['user:' . $userId] : [],
'$write' => ['role:all'],
'dateCreated' => time(),
'status' => 'processing',
'runtime' => $function->getAttribute('runtime'),
'outputPath' => '',
'source' => $tag->getAttribute('path'),
'sourceType' => Storage::DEVICE_LOCAL,
'stdout' => '',
'stderr' => '',
'time' => 0,
'vars' => [
'ENTRYPOINT_NAME' => $tag->getAttribute('entrypoint'),
'APPWRITE_FUNCTION_ID' => $function->getId(),
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name', ''),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'],
'APPWRITE_FUNCTION_PROJECT_ID' => $projectID,
]
]));
$tag->setAttribute('buildId', $buildId);
$dbForProject->updateDocument('tags', $tag->getId(), $tag);
} catch (\Throwable $th) {
var_dump($tag->getArrayCopy());
throw $th;
}
}
// Build Code
go(function () use ($projectID, $tagId, $buildId, $functionId, $function, $register) {
try {
$db = $register->get('dbPool')->get();
$redis = $register->get('redisPool')->get();
$cache = new Cache(new RedisCache($redis));
$dbForProject = new Database(new MariaDB($db), $cache);
$dbForProject->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$dbForProject->setNamespace('_project_' . $projectID);
// Build Code
runBuildStage($buildId, $projectID);
// Update the schedule
$schedule = $function->getAttribute('schedule', '');
$cron = (empty($function->getAttribute('tag')) && !empty($schedule)) ? new CronExpression($schedule) : null;
$next = (empty($function->getAttribute('tag')) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : 0;
// Grab tag
$tag = $dbForProject->getDocument('tags', $tagId);
// Grab build
$build = $dbForProject->getDocument('builds', $buildId);
// If the build failed, it won't be possible to deploy
if ($build->getAttribute('status') !== 'ready') {
return;
}
if ($tag->getAttribute('automaticDeploy') === true) {
// Update the function document setting the tag as the active one
$function
->setAttribute('tag', $tag->getId())
->setAttribute('scheduleNext', (int)$next);
$function = $dbForProject->updateDocument('functions', $function->getId(), $function);
}
// Deploy Runtime Server
createRuntimeServer($functionId, $projectID, $tagId, $dbForProject);
} catch (\Throwable $th) {
} finally {
$register->get('dbPool')->put($db);
$register->get('redisPool')->put($redis);
}
});
if (false === $function) {
throw new Exception('Failed saving function to DB', 500);
}
$response->dynamic($function, Response::MODEL_FUNCTION);
}); });
App::get('/v1/') App::get('/v1/')
@ -849,9 +1208,10 @@ App::post('/v1/build/:buildId') // Start a Build
throw new Exception('Build is already finished', 409); throw new Exception('Build is already finished', 409);
} }
Console::success('Starting build ' . $buildId); go(function () use ($buildId, $dbForProject, $projectID) {
// Build Code // Build Code
runBuildStage($buildId, $projectID, $dbForProject); runBuildStage($buildId, $projectID, $dbForProject);
});
// return success // return success
return $response->json(['success' => true]); return $response->json(['success' => true]);
@ -866,260 +1226,9 @@ App::post('/v1/build/:buildId') // Start a Build
} }
}); });
function runBuildStage(string $buildId, string $projectID): Document
{
global $runtimes;
global $orchestrationPool;
global $register;
/** @var Orchestration $orchestration */
$orchestration = $orchestrationPool->get();
$buildStdout = '';
$buildStderr = '';
$db = $register->get('dbPool')->get();
$redis = $register->get('redisPool')->get();
$cache = new Cache(new RedisCache($redis));
$database = new Database(new MariaDB($db), $cache);
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$database->setNamespace('_project_' . $projectID);
// Check if build has already been run
$build = $database->getDocument('builds', $buildId);
try {
// If we already have a built package ready there is no need to rebuild.
if ($build->getAttribute('status') === 'ready' && \file_exists($build->getAttribute('outputPath'))) {
return $build;
}
// Update Tag Status
$build->setAttribute('status', 'building');
$database->updateDocument('builds', $build->getId(), $build);
// Check if runtime is active
$runtime = $runtimes[$build->getAttribute('runtime', '')] ?? null;
if (\is_null($runtime)) {
throw new Exception('Runtime "' . $build->getAttribute('runtime', '') . '" is not supported');
}
// Grab Tag Files
$tagPath = $build->getAttribute('source', '');
$sourceType = $build->getAttribute('sourceType', '');
$device = Storage::getDevice('builds');
$tagPathTarget = '/tmp/project-' . $projectID . '/' . $build->getId() . '/code.tar.gz';
$tagPathTargetDir = \pathinfo($tagPathTarget, PATHINFO_DIRNAME);
$container = 'build-stage-' . $build->getId();
// Perform various checks
if (!\file_exists($tagPathTargetDir)) {
if (!\mkdir($tagPathTargetDir, 0777, true)) {
throw new Exception('Can\'t create directory ' . $tagPathTargetDir);
}
}
if (!\file_exists($tagPathTarget)) {
if (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) === Storage::DEVICE_LOCAL) {
if (!\copy($tagPath, $tagPathTarget)) {
throw new Exception('Can\'t create temporary code file ' . $tagPathTarget);
}
} else {
$buffer = $device->read($tagPath);
\file_put_contents($tagPathTarget, $buffer);
}
}
if (!$device->exists($tagPath)) {
throw new Exception('Code is not readable: ' . $build->getAttribute('source', ''));
}
$vars = $build->getAttribute('envVars', []);
// Start tracking time
$buildStart = \microtime(true);
$buildTime = \time();
$orchestration
->setCpus(App::getEnv('_APP_FUNCTIONS_CPUS', 0))
->setMemory(App::getEnv('_APP_FUNCTIONS_MEMORY', 256))
->setSwap(App::getEnv('_APP_FUNCTIONS_MEMORY_SWAP', 256));
foreach ($vars as &$value) {
$value = strval($value);
}
if (!\file_exists('/tmp/project-' . $projectID . '/' . $build->getId() . '/builtCode')) {
if (!\mkdir('/tmp/project-' . $projectID . '/' . $build->getId() . '/builtCode', 0777, true)) {
throw new Exception('Can\'t create directory /tmp/project-' . $projectID . '/' . $build->getId() . '/builtCode');
}
};
// Launch build container
$id = $orchestration->run(
image: $runtime['base'],
name: $container,
vars: $vars,
workdir: '/usr/code',
labels: [
'appwrite-type' => 'function',
'appwrite-created' => strval($buildTime),
'appwrite-runtime' => $build->getAttribute('runtime', ''),
'appwrite-project' => $projectID,
'appwrite-build' => $build->getId(),
],
command: [
'tail',
'-f',
'/dev/null'
],
hostname: $container,
mountFolder: $tagPathTargetDir,
volumes: [
'/tmp/project-' . $projectID . '/' . $build->getId() . '/builtCode' . ':/usr/builtCode:rw'
]
);
if (empty($id)) {
throw new Exception('Failed to start build container');
}
// Extract user code into build container
$untarStdout = '';
$untarStderr = '';
$untarSuccess = $orchestration->execute(
name: $container,
command: [
'sh',
'-c',
'mkdir -p /usr/code && cp /tmp/code.tar.gz /usr/workspace/code.tar.gz && cd /usr/workspace/ && tar -zxf /usr/workspace/code.tar.gz -C /usr/code && rm /usr/workspace/code.tar.gz'
],
stdout: $untarStdout,
stderr: $untarStderr,
timeout: 60
);
if (!$untarSuccess) {
throw new Exception('Failed to extract tar: ' . $untarStderr);
}
// Build Code / Install Dependencies
$buildSuccess = $orchestration->execute(
name: $container,
command: ['sh', '-c', 'cd /usr/local/src && ./build.sh'],
stdout: $buildStdout,
stderr: $buildStderr,
timeout: App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900)
);
if (!$buildSuccess) {
throw new Exception('Failed to build dependencies: ' . $buildStderr);
}
// Repackage Code and Save.
$compressStdout = '';
$compressStderr = '';
$builtCodePath = '/tmp/project-' . $projectID . '/' . $build->getId() . '/builtCode/code.tar.gz';
$compressSuccess = $orchestration->execute(
name: $container,
command: [
'tar', '-C', '/usr/code', '-czvf', '/usr/builtCode/code.tar.gz', './'
],
stdout: $compressStdout,
stderr: $compressStderr,
timeout: 60
);
if (!$compressSuccess) {
throw new Exception('Failed to compress built code: ' . $compressStderr);
}
// Remove Container
$orchestration->remove($id, true);
// Check if the build was successful by checking if file exists
if (!\file_exists($builtCodePath)) {
throw new Exception('Something went wrong during the build process.');
}
// Upload new code
$device = Storage::getDevice('builds');
$path = $device->getPath(\uniqid() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
if (!\file_exists(\dirname($path))) { // Checks if directory path to file exists
if (!@\mkdir(\dirname($path), 0777, true)) {
throw new Exception('Can\'t create directory: ' . \dirname($path));
}
}
if (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) === Storage::DEVICE_LOCAL) {
if (!$device->move($builtCodePath, $path)) {
throw new Exception('Failed to upload built code upload to storage', 500);
}
} else {
if (!$device->upload($builtCodePath, $path)) {
throw new Exception('Failed to upload built code upload to storage', 500);
}
}
if ($buildStdout == '') {
$buildStdout = 'Build Successful!';
}
$build
->setAttribute('outputPath', $path)
->setAttribute('status', 'ready')
->setAttribute('stdout', \utf8_encode(\mb_substr($buildStdout, -4096)))
->setAttribute('stderr', \utf8_encode(\mb_substr($buildStderr, -4096)))
->setAttribute('buildTime', $buildTime);
// Update build with built code attribute
$build = $database->updateDocument('builds', $buildId, $build);
$buildEnd = \microtime(true);
Console::info('Build Stage Ran in ' . ($buildEnd - $buildStart) . ' seconds');
} catch (Exception $e) {
$build
->setAttribute('status', 'failed')
->setAttribute('stdout', \utf8_encode(\mb_substr($buildStdout, -4096)))
->setAttribute('stderr', \utf8_encode(\mb_substr($e->getMessage(), -4096)));
$build = $database->updateDocument('builds', $buildId, $build);
// also remove the container if it exists
if (isset($id)) {
$orchestration->remove($id, true);
}
$orchestrationPool->put(null);
$register->get('dbPool')->put($db);
$register->get('redisPool')->put($redis);
throw new Exception('Build failed: ' . $e->getMessage());
} finally {
$orchestrationPool->put($orchestration);
$register->get('dbPool')->put($db);
$register->get('redisPool')->put($redis);
}
return $build;
}
App::setMode(App::MODE_TYPE_PRODUCTION); // Define Mode App::setMode(App::MODE_TYPE_PRODUCTION); // Define Mode
$http = new Server("0.0.0.0", 8080); $http = new Server("0.0.0.0", 80);
function handleShutdown() function handleShutdown()
{ {
@ -1299,4 +1408,4 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo
} }
}); });
$http->start(); $http->start();

View file

@ -685,7 +685,7 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
<input type="file" name="code" id="tag-code" size="1" required accept="application/x-gzip,.gz"> <input type="file" name="code" id="tag-code" size="1" required accept="application/x-gzip,.gz">
<div class="text-fade text-size-xs margin-top-negative-small margin-bottom">(Max file size allowed: <?php echo $fileLimitHuman; ?>)</div> <div class="text-fade text-size-xs margin-top-negative-small margin-bottom">(Max file size allowed: <?php echo $fileLimitHuman; ?>)</div>
<label for="tag-automaticDeploy" class="margin-bottom-large">Auto Deploy Tag after build <input type="checkbox" class="margin-start-small" id="tag-automaticDeploy" name="automaticDeploy" /></label> <label for="tag-deploy" class="margin-bottom-large">Auto Deploy Tag after build <input type="checkbox" class="margin-start-small" id="tag-deploy" name="deploy" /></label>
<footer> <footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button> <button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>

View file

@ -194,10 +194,10 @@ services:
- _APP_USAGE_STATS - _APP_USAGE_STATS
- _APP_STATSD_HOST - _APP_STATSD_HOST
- _APP_STATSD_PORT - _APP_STATSD_PORT
- DOCKERHUB_PULL_USERNAME
- DOCKERHUB_PULL_PASSWORD
- _APP_LOGGING_PROVIDER - _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG - _APP_LOGGING_CONFIG
- DOCKERHUB_PULL_USERNAME
- DOCKERHUB_PULL_PASSWORD
appwrite-worker-database: appwrite-worker-database:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?> image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
@ -324,10 +324,40 @@ services:
depends_on: depends_on:
- redis - redis
- mariadb - mariadb
- appwrite-executor
environment:
- _APP_ENV
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_FUNCTIONS_TIMEOUT
- _APP_EXECUTOR_SECRET
- _APP_USAGE_STATS
appwrite-executor:
container_name: appwrite-executor
entrypoint: executor
stop_signal: SIGINT
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
networks:
appwrite:
runtimes:
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- appwrite-functions:/storage/functions:rw - appwrite-functions:/storage/functions:rw
- /tmp:/tmp:rw - /tmp:/tmp:rw
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
- ./dev:/usr/local/dev
depends_on:
- redis
- mariadb
environment: environment:
- _APP_ENV - _APP_ENV
- _APP_OPENSSL_KEY_V1 - _APP_OPENSSL_KEY_V1
@ -343,12 +373,18 @@ services:
- _APP_FUNCTIONS_TIMEOUT - _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT - _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CONTAINERS - _APP_FUNCTIONS_CONTAINERS
- _APP_FUNCTIONS_RUNTIMES
- _APP_FUNCTIONS_CPUS - _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY - _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_MEMORY_SWAP - _APP_FUNCTIONS_MEMORY_SWAP
- _APP_EXECUTOR_SECRET - _APP_EXECUTOR_SECRET
- _APP_FUNCTIONS_RUNTIMES
- _APP_USAGE_STATS - _APP_USAGE_STATS
- _APP_STATSD_HOST
- _APP_STATSD_PORT
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- DOCKERHUB_PULL_USERNAME
- DOCKERHUB_PULL_PASSWORD
appwrite-worker-mails: appwrite-worker-mails:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?> image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
@ -485,6 +521,7 @@ services:
networks: networks:
gateway: gateway:
appwrite: appwrite:
runtimes:
volumes: volumes:
appwrite-mariadb: appwrite-mariadb:
@ -495,3 +532,4 @@ volumes:
appwrite-functions: appwrite-functions:
appwrite-influxdb: appwrite-influxdb:
appwrite-config: appwrite-config:
appwrite-executor:

View file

@ -1,10 +1,6 @@
<?php <?php
use Appwrite\Event\Event;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Resque\Worker; use Appwrite\Resque\Worker;
use Appwrite\Stats\Stats;
use Appwrite\Utopia\Response\Model\Execution;
use Cron\CronExpression; use Cron\CronExpression;
use Swoole\Runtime; use Swoole\Runtime;
use Utopia\App; use Utopia\App;
@ -13,11 +9,8 @@ use Utopia\Config\Config;
use Utopia\Database\Database; use Utopia\Database\Database;
use Utopia\Database\Document; use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization; use Utopia\Database\Validator\Authorization;
use Utopia\Orchestration\Orchestration;
use Utopia\Orchestration\Adapter\DockerAPI; use Utopia\Orchestration\Adapter\DockerAPI;
use Utopia\Orchestration\Container; use Utopia\Orchestration\Orchestration;
use Utopia\Orchestration\Exception\Orchestration as OrchestrationException;
use Utopia\Orchestration\Exception\Timeout as TimeoutException;
require_once __DIR__.'/../init.php'; require_once __DIR__.'/../init.php';
@ -38,41 +31,6 @@ $warmupTime = $warmupEnd - $warmupStart;
Console::success('Finished warmup in ' . $warmupTime . ' seconds'); Console::success('Finished warmup in ' . $warmupTime . ' seconds');
/**
* List function servers
*/
$stdout = '';
$stderr = '';
$executionStart = \microtime(true);
$response = $orchestration->list(['label' => 'appwrite-type=function']);
/** @var Container[] $list */
$list = [];
foreach ($response as $value) {
$list[$value->getName()] = $value;
}
$executionEnd = \microtime(true);
Console::info(count($list) . ' functions listed in ' . ($executionEnd - $executionStart) . ' seconds');
/**
* 1. Get event args - DONE
* 2. Unpackage code in the isolated container - DONE
* 3. Execute in container with timeout
* + messure execution time - DONE
* + pass env vars - DONE
* + pass one-time api key
* 4. Update execution status - DONE
* 5. Update execution stdout & stderr - DONE
* 6. Trigger audit log - DONE
* 7. Trigger usage log - DONE
*/
// TODO avoid scheduled execution if delay is bigger than X offest
class FunctionsV1 extends Worker class FunctionsV1 extends Worker
{ {
public array $args = []; public array $args = [];
@ -266,7 +224,7 @@ class FunctionsV1 extends Worker
public function execute(string $trigger, string $projectId, string $executionId, Database $database, Document $function, string $event = '', string $eventData = '', string $data = '', array $webhooks = [], string $userId = '', string $jwt = ''): void public function execute(string $trigger, string $projectId, string $executionId, Database $database, Document $function, string $event = '', string $eventData = '', string $data = '', array $webhooks = [], string $userId = '', string $jwt = ''): void
{ {
$ch = \curl_init(); $ch = \curl_init();
\curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor:8080/v1/execute"); \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/execute");
\curl_setopt($ch, CURLOPT_POST, true); \curl_setopt($ch, CURLOPT_POST, true);
\curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ \curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'trigger' => $trigger, 'trigger' => $trigger,
@ -299,67 +257,6 @@ class FunctionsV1 extends Worker
\curl_close($ch); \curl_close($ch);
} }
/**
* Cleanup any hanging containers above the allowed max containers.
*
* @return void
*/
public function cleanup(): void
{
/** @var Container[] $list */
global $list;
/** @var Orchestration $orchestration */
global $orchestration;
Console::success(count($list) . ' running containers counted');
$max = (int) App::getEnv('_APP_FUNCTIONS_CONTAINERS');
if (\count($list) > $max) {
Console::info('Starting containers cleanup');
\uasort($list, function (Container $item1, Container $item2) {
return (int)($item1->getLabels['appwrite-created'] ?? 0) <=> (int)($item2->getLabels['appwrite-created'] ?? 0);
});
while (\count($list) > $max) {
$first = \array_shift($list);
try {
$orchestration->remove($first->getName(), true);
Console::info('Removed container: ' . $first->getName());
} catch (Exception $e) {
Console::error('Failed to remove container: ' . $e);
}
}
}
}
/**
* Filter ENV vars
*
* @param string $string
*
* @return string
*/
public function filterEnvKey(string $string): string
{
if (empty($this->allowed)) {
$this->allowed = array_fill_keys(\str_split('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_'), true);
}
$string = \str_split($string);
$output = '';
foreach ($string as $char) {
if (\array_key_exists($char, $this->allowed)) {
$output .= $char;
}
}
return $output;
}
public function shutdown(): void public function shutdown(): void
{ {
} }

3
bin/executor Normal file
View file

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

View file

@ -61,7 +61,6 @@ services:
- traefik.http.routers.appwrite_api_https.service=appwrite_api - traefik.http.routers.appwrite_api_https.service=appwrite_api
- traefik.http.routers.appwrite_api_https.tls=true - traefik.http.routers.appwrite_api_https.tls=true
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock
- appwrite-uploads:/storage/uploads:rw - appwrite-uploads:/storage/uploads:rw
- appwrite-cache:/storage/cache:rw - appwrite-cache:/storage/cache:rw
- appwrite-config:/storage/config:rw - appwrite-config:/storage/config:rw
@ -354,17 +353,14 @@ services:
networks: networks:
- appwrite - appwrite
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock
- appwrite-functions:/storage/functions:rw
- /tmp:/tmp:rw
- ./app:/usr/src/code/app - ./app:/usr/src/code/app
- ./src:/usr/src/code/src - ./src:/usr/src/code/src
depends_on: depends_on:
- redis - redis
- mariadb - mariadb
- appwrite-executor
environment: environment:
- _APP_ENV - _APP_ENV
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST - _APP_REDIS_HOST
- _APP_REDIS_PORT - _APP_REDIS_PORT
- _APP_REDIS_USER - _APP_REDIS_USER
@ -375,12 +371,6 @@ services:
- _APP_DB_USER - _APP_DB_USER
- _APP_DB_PASS - _APP_DB_PASS
- _APP_FUNCTIONS_TIMEOUT - _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CONTAINERS
- _APP_FUNCTIONS_RUNTIMES
- _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY
- _APP_FUNCTIONS_MEMORY_SWAP
- _APP_EXECUTOR_SECRET - _APP_EXECUTOR_SECRET
- _APP_USAGE_STATS - _APP_USAGE_STATS
- DOCKERHUB_PULL_USERNAME - DOCKERHUB_PULL_USERNAME
@ -388,14 +378,8 @@ services:
appwrite-executor: appwrite-executor:
container_name: appwrite-executor container_name: appwrite-executor
entrypoint: entrypoint: executor
- php
- -e
- /usr/src/code/app/executor.php
- -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
stop_signal: SIGINT stop_signal: SIGINT
ports:
- "8080:8080"
build: build:
context: . context: .
args: args:
@ -438,10 +422,10 @@ services:
- _APP_USAGE_STATS - _APP_USAGE_STATS
- _APP_STATSD_HOST - _APP_STATSD_HOST
- _APP_STATSD_PORT - _APP_STATSD_PORT
- DOCKERHUB_PULL_USERNAME
- DOCKERHUB_PULL_PASSWORD
- _APP_LOGGING_PROVIDER - _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG - _APP_LOGGING_CONFIG
- DOCKERHUB_PULL_USERNAME
- DOCKERHUB_PULL_PASSWORD
appwrite-worker-mails: appwrite-worker-mails:
entrypoint: worker-mails entrypoint: worker-mails

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,29 +1,29 @@
(function (exports, isomorphicFormData, crossFetch) { (function (exports, isomorphicFormData, crossFetch) {
'use strict'; 'use strict';
/*! ***************************************************************************** /*! *****************************************************************************
Copyright (c) Microsoft Corporation. Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted. purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */ ***************************************************************************** */
function __awaiter(thisArg, _arguments, P, generator) { function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) { return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next()); step((generator = generator.apply(thisArg, _arguments || [])).next());
}); });
} }
class AppwriteException extends Error { class AppwriteException extends Error {
@ -38,7 +38,7 @@
class Appwrite { class Appwrite {
constructor() { constructor() {
this.config = { this.config = {
endpoint: 'https://appwrite.io/v1', endpoint: 'https://HOSTNAME/v1',
endpointRealtime: '', endpointRealtime: '',
project: '', project: '',
key: '', key: '',
@ -48,7 +48,7 @@
}; };
this.headers = { this.headers = {
'x-sdk-version': 'appwrite:web:4.0.4', 'x-sdk-version': 'appwrite:web:4.0.4',
'X-Appwrite-Response-Format': '0.11.0', 'X-Appwrite-Response-Format': '0.12.0',
}; };
this.realtime = { this.realtime = {
socket: undefined, socket: undefined,
@ -264,12 +264,14 @@
* Update Account Email * Update Account Email
* *
* Update currently logged in user account email address. After changing user * Update currently logged in user account email address. After changing user
* address, user confirmation status is being reset and a new confirmation * address, the user confirmation status will get reset. A new confirmation
* mail is sent. For security measures, user password is required to complete * email is not sent automatically however you can use the send confirmation
* this request. * email endpoint again to send the confirmation email. For security measures,
* user password is required to complete this request.
* This endpoint can also be used to convert an anonymous account to a normal * This endpoint can also be used to convert an anonymous account to a normal
* one, by passing an email address and a new password. * one, by passing an email address and a new password.
* *
*
* @param {string} email * @param {string} email
* @param {string} password * @param {string} password
* @throws {AppwriteException} * @throws {AppwriteException}
@ -411,8 +413,9 @@
/** /**
* Update Account Preferences * Update Account Preferences
* *
* Update currently logged in user account preferences. You can pass only the * Update currently logged in user account preferences. The object you pass is
* specific settings you wish to update. * stored as is, and replaces any previous value. The maximum allowed prefs
* size is 64kB and throws error if exceeded.
* *
* @param {object} prefs * @param {object} prefs
* @throws {AppwriteException} * @throws {AppwriteException}
@ -766,7 +769,8 @@
* *
* Use this endpoint to log out the currently logged in user from all their * 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 * account sessions across all of their different devices. When using the
* option id argument, only the session unique ID provider will be deleted. * Session ID argument, only the unique session ID provided is deleted.
*
* *
* @param {string} sessionId * @param {string} sessionId
* @throws {AppwriteException} * @throws {AppwriteException}
@ -1115,7 +1119,7 @@
}; };
this.functions = { this.functions = {
/** /**
* Get Builds * List Builds
* *
* Get a list of all the current user build logs. You can use the query params * Get a list of all the current user build logs. You can use the query params
* to filter your results. On admin mode, this endpoint will return a list of * to filter your results. On admin mode, this endpoint will return a list of
@ -1294,6 +1298,22 @@
'content-type': 'application/json', 'content-type': 'application/json',
}, payload); }, payload);
}), }),
/**
* List the currently active function runtimes.
*
* Get a list of all runtimes that are currently active in your project.
*
* @throws {AppwriteException}
* @returns {Promise}
*/
listRuntimes: () => __awaiter(this, void 0, void 0, function* () {
let path = '/functions/runtimes';
let payload = {};
const uri = new URL(this.config.endpoint + path);
return yield this.call('get', uri, {
'content-type': 'application/json',
}, payload);
}),
/** /**
* Get Function * Get Function
* *
@ -1573,11 +1593,11 @@
* @param {string} functionId * @param {string} functionId
* @param {string} entrypoint * @param {string} entrypoint
* @param {File} code * @param {File} code
* @param {boolean} automaticDeploy * @param {boolean} deploy
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createTag: (functionId, entrypoint, code, automaticDeploy) => __awaiter(this, void 0, void 0, function* () { createTag: (functionId, entrypoint, code, deploy) => __awaiter(this, void 0, void 0, function* () {
if (typeof functionId === 'undefined') { if (typeof functionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "functionId"'); throw new AppwriteException('Missing required parameter: "functionId"');
} }
@ -1587,8 +1607,8 @@
if (typeof code === 'undefined') { if (typeof code === 'undefined') {
throw new AppwriteException('Missing required parameter: "code"'); throw new AppwriteException('Missing required parameter: "code"');
} }
if (typeof automaticDeploy === 'undefined') { if (typeof deploy === 'undefined') {
throw new AppwriteException('Missing required parameter: "automaticDeploy"'); throw new AppwriteException('Missing required parameter: "deploy"');
} }
let path = '/functions/{functionId}/tags'.replace('{functionId}', functionId); let path = '/functions/{functionId}/tags'.replace('{functionId}', functionId);
let payload = {}; let payload = {};
@ -1598,8 +1618,8 @@
if (typeof code !== 'undefined') { if (typeof code !== 'undefined') {
payload['code'] = code; payload['code'] = code;
} }
if (typeof automaticDeploy !== 'undefined') { if (typeof deploy !== 'undefined') {
payload['automaticDeploy'] = automaticDeploy; payload['deploy'] = deploy;
} }
const uri = new URL(this.config.endpoint + path); const uri = new URL(this.config.endpoint + path);
return yield this.call('post', uri, { return yield this.call('post', uri, {
@ -1804,10 +1824,11 @@
* @param {string} permission * @param {string} permission
* @param {string[]} read * @param {string[]} read
* @param {string[]} write * @param {string[]} write
* @param {boolean} enabled
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
updateCollection: (collectionId, name, permission, read, write) => __awaiter(this, void 0, void 0, function* () { updateCollection: (collectionId, name, permission, read, write, enabled) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
@ -1831,6 +1852,9 @@
if (typeof write !== 'undefined') { if (typeof write !== 'undefined') {
payload['write'] = write; payload['write'] = write;
} }
if (typeof enabled !== 'undefined') {
payload['enabled'] = enabled;
}
const uri = new URL(this.config.endpoint + path); const uri = new URL(this.config.endpoint + path);
return yield this.call('put', uri, { return yield this.call('put', uri, {
'content-type': 'application/json', 'content-type': 'application/json',
@ -1883,27 +1907,27 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @param {boolean} required * @param {boolean} required
* @param {boolean} xdefault * @param {boolean} xdefault
* @param {boolean} array * @param {boolean} array
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createBooleanAttribute: (collectionId, attributeId, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () { createBooleanAttribute: (collectionId, key, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
if (typeof required === 'undefined') { if (typeof required === 'undefined') {
throw new AppwriteException('Missing required parameter: "required"'); throw new AppwriteException('Missing required parameter: "required"');
} }
let path = '/database/collections/{collectionId}/attributes/boolean'.replace('{collectionId}', collectionId); let path = '/database/collections/{collectionId}/attributes/boolean'.replace('{collectionId}', collectionId);
let payload = {}; let payload = {};
if (typeof attributeId !== 'undefined') { if (typeof key !== 'undefined') {
payload['attributeId'] = attributeId; payload['key'] = key;
} }
if (typeof required !== 'undefined') { if (typeof required !== 'undefined') {
payload['required'] = required; payload['required'] = required;
@ -1926,27 +1950,27 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @param {boolean} required * @param {boolean} required
* @param {string} xdefault * @param {string} xdefault
* @param {boolean} array * @param {boolean} array
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createEmailAttribute: (collectionId, attributeId, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () { createEmailAttribute: (collectionId, key, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
if (typeof required === 'undefined') { if (typeof required === 'undefined') {
throw new AppwriteException('Missing required parameter: "required"'); throw new AppwriteException('Missing required parameter: "required"');
} }
let path = '/database/collections/{collectionId}/attributes/email'.replace('{collectionId}', collectionId); let path = '/database/collections/{collectionId}/attributes/email'.replace('{collectionId}', collectionId);
let payload = {}; let payload = {};
if (typeof attributeId !== 'undefined') { if (typeof key !== 'undefined') {
payload['attributeId'] = attributeId; payload['key'] = key;
} }
if (typeof required !== 'undefined') { if (typeof required !== 'undefined') {
payload['required'] = required; payload['required'] = required;
@ -1967,7 +1991,7 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @param {string[]} elements * @param {string[]} elements
* @param {boolean} required * @param {boolean} required
* @param {string} xdefault * @param {string} xdefault
@ -1975,12 +1999,12 @@
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createEnumAttribute: (collectionId, attributeId, elements, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () { createEnumAttribute: (collectionId, key, elements, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
if (typeof elements === 'undefined') { if (typeof elements === 'undefined') {
throw new AppwriteException('Missing required parameter: "elements"'); throw new AppwriteException('Missing required parameter: "elements"');
@ -1990,8 +2014,8 @@
} }
let path = '/database/collections/{collectionId}/attributes/enum'.replace('{collectionId}', collectionId); let path = '/database/collections/{collectionId}/attributes/enum'.replace('{collectionId}', collectionId);
let payload = {}; let payload = {};
if (typeof attributeId !== 'undefined') { if (typeof key !== 'undefined') {
payload['attributeId'] = attributeId; payload['key'] = key;
} }
if (typeof elements !== 'undefined') { if (typeof elements !== 'undefined') {
payload['elements'] = elements; payload['elements'] = elements;
@ -2018,7 +2042,7 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @param {boolean} required * @param {boolean} required
* @param {string} min * @param {string} min
* @param {string} max * @param {string} max
@ -2027,20 +2051,20 @@
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createFloatAttribute: (collectionId, attributeId, required, min, max, xdefault, array) => __awaiter(this, void 0, void 0, function* () { createFloatAttribute: (collectionId, key, required, min, max, xdefault, array) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
if (typeof required === 'undefined') { if (typeof required === 'undefined') {
throw new AppwriteException('Missing required parameter: "required"'); throw new AppwriteException('Missing required parameter: "required"');
} }
let path = '/database/collections/{collectionId}/attributes/float'.replace('{collectionId}', collectionId); let path = '/database/collections/{collectionId}/attributes/float'.replace('{collectionId}', collectionId);
let payload = {}; let payload = {};
if (typeof attributeId !== 'undefined') { if (typeof key !== 'undefined') {
payload['attributeId'] = attributeId; payload['key'] = key;
} }
if (typeof required !== 'undefined') { if (typeof required !== 'undefined') {
payload['required'] = required; payload['required'] = required;
@ -2070,7 +2094,7 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @param {boolean} required * @param {boolean} required
* @param {number} min * @param {number} min
* @param {number} max * @param {number} max
@ -2079,20 +2103,20 @@
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createIntegerAttribute: (collectionId, attributeId, required, min, max, xdefault, array) => __awaiter(this, void 0, void 0, function* () { createIntegerAttribute: (collectionId, key, required, min, max, xdefault, array) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
if (typeof required === 'undefined') { if (typeof required === 'undefined') {
throw new AppwriteException('Missing required parameter: "required"'); throw new AppwriteException('Missing required parameter: "required"');
} }
let path = '/database/collections/{collectionId}/attributes/integer'.replace('{collectionId}', collectionId); let path = '/database/collections/{collectionId}/attributes/integer'.replace('{collectionId}', collectionId);
let payload = {}; let payload = {};
if (typeof attributeId !== 'undefined') { if (typeof key !== 'undefined') {
payload['attributeId'] = attributeId; payload['key'] = key;
} }
if (typeof required !== 'undefined') { if (typeof required !== 'undefined') {
payload['required'] = required; payload['required'] = required;
@ -2121,27 +2145,27 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @param {boolean} required * @param {boolean} required
* @param {string} xdefault * @param {string} xdefault
* @param {boolean} array * @param {boolean} array
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createIpAttribute: (collectionId, attributeId, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () { createIpAttribute: (collectionId, key, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
if (typeof required === 'undefined') { if (typeof required === 'undefined') {
throw new AppwriteException('Missing required parameter: "required"'); throw new AppwriteException('Missing required parameter: "required"');
} }
let path = '/database/collections/{collectionId}/attributes/ip'.replace('{collectionId}', collectionId); let path = '/database/collections/{collectionId}/attributes/ip'.replace('{collectionId}', collectionId);
let payload = {}; let payload = {};
if (typeof attributeId !== 'undefined') { if (typeof key !== 'undefined') {
payload['attributeId'] = attributeId; payload['key'] = key;
} }
if (typeof required !== 'undefined') { if (typeof required !== 'undefined') {
payload['required'] = required; payload['required'] = required;
@ -2160,11 +2184,11 @@
/** /**
* Create String Attribute * Create String Attribute
* *
* Create a new string attribute. * Create a string attribute.
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @param {number} size * @param {number} size
* @param {boolean} required * @param {boolean} required
* @param {string} xdefault * @param {string} xdefault
@ -2172,12 +2196,12 @@
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createStringAttribute: (collectionId, attributeId, size, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () { createStringAttribute: (collectionId, key, size, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
if (typeof size === 'undefined') { if (typeof size === 'undefined') {
throw new AppwriteException('Missing required parameter: "size"'); throw new AppwriteException('Missing required parameter: "size"');
@ -2187,8 +2211,8 @@
} }
let path = '/database/collections/{collectionId}/attributes/string'.replace('{collectionId}', collectionId); let path = '/database/collections/{collectionId}/attributes/string'.replace('{collectionId}', collectionId);
let payload = {}; let payload = {};
if (typeof attributeId !== 'undefined') { if (typeof key !== 'undefined') {
payload['attributeId'] = attributeId; payload['key'] = key;
} }
if (typeof size !== 'undefined') { if (typeof size !== 'undefined') {
payload['size'] = size; payload['size'] = size;
@ -2214,27 +2238,27 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @param {boolean} required * @param {boolean} required
* @param {string} xdefault * @param {string} xdefault
* @param {boolean} array * @param {boolean} array
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createUrlAttribute: (collectionId, attributeId, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () { createUrlAttribute: (collectionId, key, required, xdefault, array) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
if (typeof required === 'undefined') { if (typeof required === 'undefined') {
throw new AppwriteException('Missing required parameter: "required"'); throw new AppwriteException('Missing required parameter: "required"');
} }
let path = '/database/collections/{collectionId}/attributes/url'.replace('{collectionId}', collectionId); let path = '/database/collections/{collectionId}/attributes/url'.replace('{collectionId}', collectionId);
let payload = {}; let payload = {};
if (typeof attributeId !== 'undefined') { if (typeof key !== 'undefined') {
payload['attributeId'] = attributeId; payload['key'] = key;
} }
if (typeof required !== 'undefined') { if (typeof required !== 'undefined') {
payload['required'] = required; payload['required'] = required;
@ -2255,18 +2279,18 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
getAttribute: (collectionId, attributeId) => __awaiter(this, void 0, void 0, function* () { getAttribute: (collectionId, key) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
let path = '/database/collections/{collectionId}/attributes/{attributeId}'.replace('{collectionId}', collectionId).replace('{attributeId}', attributeId); let path = '/database/collections/{collectionId}/attributes/{key}'.replace('{collectionId}', collectionId).replace('{key}', key);
let payload = {}; let payload = {};
const uri = new URL(this.config.endpoint + path); const uri = new URL(this.config.endpoint + path);
return yield this.call('get', uri, { return yield this.call('get', uri, {
@ -2278,18 +2302,18 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} attributeId * @param {string} key
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
deleteAttribute: (collectionId, attributeId) => __awaiter(this, void 0, void 0, function* () { deleteAttribute: (collectionId, key) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof attributeId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "attributeId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
let path = '/database/collections/{collectionId}/attributes/{attributeId}'.replace('{collectionId}', collectionId).replace('{attributeId}', attributeId); let path = '/database/collections/{collectionId}/attributes/{key}'.replace('{collectionId}', collectionId).replace('{key}', key);
let payload = {}; let payload = {};
const uri = new URL(this.config.endpoint + path); const uri = new URL(this.config.endpoint + path);
return yield this.call('delete', uri, { return yield this.call('delete', uri, {
@ -2539,19 +2563,19 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} indexId * @param {string} key
* @param {string} type * @param {string} type
* @param {string[]} attributes * @param {string[]} attributes
* @param {string[]} orders * @param {string[]} orders
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
createIndex: (collectionId, indexId, type, attributes, orders) => __awaiter(this, void 0, void 0, function* () { createIndex: (collectionId, key, type, attributes, orders) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof indexId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "indexId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
if (typeof type === 'undefined') { if (typeof type === 'undefined') {
throw new AppwriteException('Missing required parameter: "type"'); throw new AppwriteException('Missing required parameter: "type"');
@ -2561,8 +2585,8 @@
} }
let path = '/database/collections/{collectionId}/indexes'.replace('{collectionId}', collectionId); let path = '/database/collections/{collectionId}/indexes'.replace('{collectionId}', collectionId);
let payload = {}; let payload = {};
if (typeof indexId !== 'undefined') { if (typeof key !== 'undefined') {
payload['indexId'] = indexId; payload['key'] = key;
} }
if (typeof type !== 'undefined') { if (typeof type !== 'undefined') {
payload['type'] = type; payload['type'] = type;
@ -2583,18 +2607,18 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} indexId * @param {string} key
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
getIndex: (collectionId, indexId) => __awaiter(this, void 0, void 0, function* () { getIndex: (collectionId, key) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof indexId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "indexId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
let path = '/database/collections/{collectionId}/indexes/{indexId}'.replace('{collectionId}', collectionId).replace('{indexId}', indexId); let path = '/database/collections/{collectionId}/indexes/{key}'.replace('{collectionId}', collectionId).replace('{key}', key);
let payload = {}; let payload = {};
const uri = new URL(this.config.endpoint + path); const uri = new URL(this.config.endpoint + path);
return yield this.call('get', uri, { return yield this.call('get', uri, {
@ -2606,18 +2630,18 @@
* *
* *
* @param {string} collectionId * @param {string} collectionId
* @param {string} indexId * @param {string} key
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
deleteIndex: (collectionId, indexId) => __awaiter(this, void 0, void 0, function* () { deleteIndex: (collectionId, key) => __awaiter(this, void 0, void 0, function* () {
if (typeof collectionId === 'undefined') { if (typeof collectionId === 'undefined') {
throw new AppwriteException('Missing required parameter: "collectionId"'); throw new AppwriteException('Missing required parameter: "collectionId"');
} }
if (typeof indexId === 'undefined') { if (typeof key === 'undefined') {
throw new AppwriteException('Missing required parameter: "indexId"'); throw new AppwriteException('Missing required parameter: "key"');
} }
let path = '/database/collections/{collectionId}/indexes/{indexId}'.replace('{collectionId}', collectionId).replace('{indexId}', indexId); let path = '/database/collections/{collectionId}/indexes/{key}'.replace('{collectionId}', collectionId).replace('{key}', key);
let payload = {}; let payload = {};
const uri = new URL(this.config.endpoint + path); const uri = new URL(this.config.endpoint + path);
return yield this.call('delete', uri, { return yield this.call('delete', uri, {
@ -2713,14 +2737,14 @@
}, payload); }, payload);
}), }),
/** /**
* Get Anti virus * Get Antivirus
* *
* Check the Appwrite Anti Virus server is up and connection is successful. * Check the Appwrite Antivirus server is up and connection is successful.
* *
* @throws {AppwriteException} * @throws {AppwriteException}
* @returns {Promise} * @returns {Promise}
*/ */
getAntiVirus: () => __awaiter(this, void 0, void 0, function* () { getAntivirus: () => __awaiter(this, void 0, void 0, function* () {
let path = '/health/anti-virus'; let path = '/health/anti-virus';
let payload = {}; let payload = {};
const uri = new URL(this.config.endpoint + path); const uri = new URL(this.config.endpoint + path);
@ -4280,10 +4304,11 @@
/** /**
* List Teams * List Teams
* *
* Get a list of all the current user teams. You can use the query params to * Get a list of all the teams in which the current user is a member. You can
* filter your results. On admin mode, this endpoint will return a list of all * use the parameters to filter your results.
* of the project's teams. [Learn more about different API *
* modes](/docs/admin). * 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} search * @param {string} search
* @param {number} limit * @param {number} limit
@ -4324,9 +4349,8 @@
* Create Team * Create Team
* *
* Create a new team. The user who creates the team will automatically be * Create a new team. The user who creates the team will automatically be
* assigned as the owner of the team. The team owner can invite new members, * assigned as the owner of the team. Only the users with the owner role can
* who will be able add new owners and update or delete the team from your * invite new members, add new owners and delete or update the team.
* project.
* *
* @param {string} teamId * @param {string} teamId
* @param {string} name * @param {string} name
@ -4360,8 +4384,7 @@
/** /**
* Get Team * Get Team
* *
* Get a team by its unique ID. All team members have read access for this * Get a team by its ID. All team members have read access for this resource.
* resource.
* *
* @param {string} teamId * @param {string} teamId
* @throws {AppwriteException} * @throws {AppwriteException}
@ -4381,8 +4404,8 @@
/** /**
* Update Team * Update Team
* *
* Update a team by its unique ID. Only team owners have write access for this * Update a team using its ID. Only members with the owner role can update the
* resource. * team.
* *
* @param {string} teamId * @param {string} teamId
* @param {string} name * @param {string} name
@ -4409,8 +4432,8 @@
/** /**
* Delete Team * Delete Team
* *
* Delete a team by its unique ID. Only team owners have write access for this * Delete a team using its ID. Only team members with the owner role can
* resource. * delete the team.
* *
* @param {string} teamId * @param {string} teamId
* @throws {AppwriteException} * @throws {AppwriteException}
@ -4430,8 +4453,8 @@
/** /**
* Get Team Memberships * Get Team Memberships
* *
* Get a team members by the team unique ID. All team members have read access * Use this endpoint to list a team's members using the team's ID. All team
* for this list of resources. * members have read access to this endpoint.
* *
* @param {string} teamId * @param {string} teamId
* @param {string} search * @param {string} search
@ -4475,22 +4498,21 @@
/** /**
* Create Team Membership * Create Team Membership
* *
* Use this endpoint to invite a new member to join your team. If initiated * Invite a new member to join your team. If initiated from the client SDK, an
* from Client SDK, an email with a link to join the team will be sent to the * email with a link to join the team will be sent to the member's email
* new member's email address if the member doesn't exist in the project it * address and an account will be created for them should they not be signed
* will be created automatically. If initiated from server side SDKs, new * up already. If initiated from server-side SDKs, the new member will
* member will automatically be added to the team. * automatically be added to the team.
* *
* Use the 'URL' parameter to redirect the user from the invitation email back * Use the 'url' parameter to redirect the user from the invitation email back
* to your app. When the user is redirected, use the [Update Team Membership * to your app. When the user is redirected, use the [Update Team Membership
* Status](/docs/client/teams#teamsUpdateMembershipStatus) endpoint to allow * Status](/docs/client/teams#teamsUpdateMembershipStatus) endpoint to allow
* the user to accept the invitation to the team. While calling from side * the user to accept the invitation to the team.
* SDKs the redirect url can be empty string.
* *
* Please note that in order to avoid a [Redirect * Please note that to avoid a [Redirect
* Attacks](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) * Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
* the only valid redirect URL's are the once from domains you have set when * the only valid redirect URL's are the once from domains you have set when
* added your platforms in the console interface. * adding your platforms in the console interface.
* *
* @param {string} teamId * @param {string} teamId
* @param {string} email * @param {string} email
@ -4560,6 +4582,9 @@
/** /**
* Update Membership Roles * Update Membership Roles
* *
* Modify the roles of a team member. Only team members with the owner role
* have access to this endpoint. Learn more about [roles and
* permissions](/docs/permissions).
* *
* @param {string} teamId * @param {string} teamId
* @param {string} membershipId * @param {string} membershipId
@ -4617,9 +4642,13 @@
* Update Team Membership Status * Update Team Membership Status
* *
* Use this endpoint to allow a user to accept an invitation to join a team * Use this endpoint to allow a user to accept an invitation to join a team
* after being redirected back to your app from the invitation email recieved * after being redirected back to your app from the invitation email received
* by the user. * by the user.
* *
* If the request is successful, a session for the user is automatically
* created.
*
*
* @param {string} teamId * @param {string} teamId
* @param {string} membershipId * @param {string} membershipId
* @param {string} userId * @param {string} userId
@ -4932,8 +4961,9 @@
/** /**
* Update User Preferences * Update User Preferences
* *
* Update the user preferences by its unique ID. You can pass only the * Update the user preferences by its unique ID. The object you pass is stored
* specific settings you wish to update. * as is, and replaces any previous value. The maximum allowed prefs size is
* 64kB and throws error if exceeded.
* *
* @param {string} userId * @param {string} userId
* @param {object} prefs * @param {object} prefs
@ -5292,8 +5322,26 @@
return output; return output;
} }
} }
class Query {
}
Query.equal = (attribute, value) => Query.addQuery(attribute, "equal", value);
Query.notEqual = (attribute, value) => Query.addQuery(attribute, "notEqual", value);
Query.lesser = (attribute, value) => Query.addQuery(attribute, "lesser", value);
Query.lesserEqual = (attribute, value) => Query.addQuery(attribute, "lesserEqual", value);
Query.greater = (attribute, value) => Query.addQuery(attribute, "greater", value);
Query.greaterEqual = (attribute, value) => Query.addQuery(attribute, "greaterEqual", value);
Query.search = (attribute, value) => Query.addQuery(attribute, "search", value);
Query.addQuery = (attribute, oper, value) => value instanceof Array
? `${attribute}.${oper}(${value
.map((v) => Query.parseValues(v))
.join(",")})`
: `${attribute}.${oper}(${Query.parseValues(value)})`;
Query.parseValues = (value) => typeof value === "string" || value instanceof String
? `"${value}"`
: `${value}`;
exports.Appwrite = Appwrite; exports.Appwrite = Appwrite;
exports.Query = Query;
Object.defineProperty(exports, '__esModule', { value: true }); Object.defineProperty(exports, '__esModule', { value: true });

View file

@ -45,7 +45,7 @@ class Build extends Model
'default' => '', 'default' => '',
'example' => '', 'example' => '',
]) ])
->addRule('buildTime', [ ->addRule('time', [
'type' => self::TYPE_INTEGER, 'type' => self::TYPE_INTEGER,
'description' => 'The build time in seconds.', 'description' => 'The build time in seconds.',
'default' => 0, 'default' => 0,

View file

@ -69,7 +69,7 @@ class Tag extends Model
'default' => '', 'default' => '',
'example' => '', 'example' => '',
]) ])
->addRule('automaticDeploy', [ ->addRule('deploy', [
'type' => self::TYPE_BOOLEAN, 'type' => self::TYPE_BOOLEAN,
'description' => 'Whether the tag should be automatically deployed.', 'description' => 'Whether the tag should be automatically deployed.',
'default' => false, 'default' => false,

View file

@ -331,6 +331,38 @@ class FunctionsCustomServerTest extends Scope
return $data; return $data;
} }
/**
* @depends testCreateTag
*/
public function testListBuild(array $data):array
{
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/builds', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(1, $response['body']['sum']);
$this->assertEquals($response['body']['builds'][0]['status'], 'ready');
/**
* Check Queries work
*/
$responseQuery = $this->client->call(Client::METHOD_GET, '/builds?status=status.equal(\"ready\")', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $responseQuery['headers']['status-code']);
$this->assertEquals(1, $responseQuery['body']['sum']);
$this->assertEquals($responseQuery['body']['builds'][0]['status'], 'ready');
return $data;
}
/** /**
* @depends testCreateTag * @depends testCreateTag
*/ */

View file

@ -84,7 +84,6 @@ services:
- _APP_STORAGE_ANTIVIRUS=disabled - _APP_STORAGE_ANTIVIRUS=disabled
- _APP_STORAGE_LIMIT - _APP_STORAGE_LIMIT
- _APP_FUNCTIONS_TIMEOUT - _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CONTAINERS - _APP_FUNCTIONS_CONTAINERS
- _APP_FUNCTIONS_CPUS - _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY - _APP_FUNCTIONS_MEMORY
@ -244,7 +243,6 @@ services:
- _APP_DB_USER - _APP_DB_USER
- _APP_DB_PASS - _APP_DB_PASS
- _APP_FUNCTIONS_TIMEOUT - _APP_FUNCTIONS_TIMEOUT
- _APP_FUNCTIONS_BUILD_TIMEOUT
- _APP_FUNCTIONS_CONTAINERS - _APP_FUNCTIONS_CONTAINERS
- _APP_FUNCTIONS_CPUS - _APP_FUNCTIONS_CPUS
- _APP_FUNCTIONS_MEMORY - _APP_FUNCTIONS_MEMORY