1
0
Fork 0
mirror of synced 2024-07-07 15:36:19 +12:00
appwrite/app/controllers/api/functions.php

2064 lines
93 KiB
PHP
Raw Normal View History

2020-05-05 02:35:01 +12:00
<?php
2021-03-11 06:48:05 +13:00
use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
2022-04-20 01:13:55 +12:00
use Appwrite\Event\Build;
2022-05-25 02:28:27 +12:00
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
2022-11-16 07:13:17 +13:00
use Appwrite\Event\Func;
2022-12-15 22:45:43 +13:00
use Appwrite\Event\Usage;
use Appwrite\Event\Validator\Event as ValidatorEvent;
2023-03-11 01:20:24 +13:00
use Appwrite\Utopia\Response\Model\Rule;
2022-02-19 01:36:24 +13:00
use Appwrite\Extend\Exception;
2022-01-19 00:05:04 +13:00
use Appwrite\Utopia\Database\Validator\CustomId;
2023-03-11 01:20:24 +13:00
use Appwrite\Messaging\Adapter\Realtime;
2023-02-15 00:01:38 +13:00
use Utopia\Validator\Assoc;
2022-08-09 18:28:38 +12:00
use Appwrite\Usage\Stats;
2022-12-15 04:42:25 +13:00
use Utopia\Database\Helpers\ID;
2022-12-15 05:04:06 +13:00
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
2021-07-26 02:47:18 +12:00
use Utopia\Database\Validator\UID;
2022-05-25 02:28:27 +12:00
use Utopia\Storage\Device;
2021-01-22 21:28:33 +13:00
use Utopia\Storage\Validator\File;
2021-01-28 02:15:44 +13:00
use Utopia\Storage\Validator\FileExt;
2021-01-22 21:28:33 +13:00
use Utopia\Storage\Validator\FileSize;
use Utopia\Storage\Validator\Upload;
2020-10-31 08:53:27 +13:00
use Appwrite\Utopia\Response;
2022-05-25 02:28:27 +12:00
use Utopia\Swoole\Request;
2020-05-06 05:30:12 +12:00
use Appwrite\Task\Validator\Cron;
2022-08-23 21:12:48 +12:00
use Appwrite\Utopia\Database\Validator\Queries\Deployments;
2022-08-23 21:26:34 +12:00
use Appwrite\Utopia\Database\Validator\Queries\Executions;
2022-08-23 21:02:06 +12:00
use Appwrite\Utopia\Database\Validator\Queries\Functions;
2020-07-13 09:18:52 +12:00
use Utopia\App;
2021-06-13 02:33:23 +12:00
use Utopia\Database\Database;
2021-05-05 09:25:17 +12:00
use Utopia\Database\Document;
use Utopia\Database\DateTime;
2021-05-05 09:25:17 +12:00
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
2020-05-06 05:30:12 +12:00
use Utopia\Validator\ArrayList;
2020-05-05 02:35:01 +12:00
use Utopia\Validator\Text;
use Utopia\Validator\Range;
2020-05-06 05:30:12 +12:00
use Utopia\Validator\WhiteList;
2020-07-15 04:13:18 +12:00
use Utopia\Config\Config;
2022-02-05 23:08:05 +13:00
use Executor\Executor;
use Utopia\CLI\Console;
use Utopia\Database\Validator\Roles;
2021-12-07 04:04:00 +13:00
use Utopia\Validator\Boolean;
2022-08-03 04:11:15 +12:00
use Utopia\Database\Exception\Duplicate as DuplicateException;
use MaxMind\Db\Reader;
2023-08-10 03:53:58 +12:00
use Utopia\VCS\Adapter\Git\GitHub;
2020-05-05 02:35:01 +12:00
include_once __DIR__ . '/../shared/api.php';
2023-08-10 03:53:58 +12:00
$redeployVcs = function (Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Document $template, GitHub $github) {
2023-06-17 02:43:37 +12:00
$deploymentId = ID::unique();
$entrypoint = $function->getAttribute('entrypoint', '');
2023-08-10 03:53:58 +12:00
$providerInstallationId = $installation->getAttribute('providerInstallationId', '');
$privateKey = App::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
$githubAppId = App::getEnv('_APP_VCS_GITHUB_APP_ID');
2023-08-10 05:35:23 +12:00
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
2023-08-10 03:53:58 +12:00
$owner = $github->getOwnerName($providerInstallationId);
$providerRepositoryId = $function->getAttribute('providerRepositoryId', '');
$repositoryName = $github->getRepositoryName($providerRepositoryId);
$providerBranch = $function->getAttribute('providerBranch', 'main');
2023-08-10 05:35:23 +12:00
$authorUrl = "https://github.com/$owner";
$repositoryUrl = "https://github.com/$owner/$repositoryName";
$branchUrl = "https://github.com/$owner/$repositoryName/tree/$providerBranch";
2023-08-10 12:31:49 +12:00
$commitDetails = [];
2023-08-11 00:22:10 +12:00
if ($template->isEmpty()) {
2023-08-10 12:31:49 +12:00
try {
$commitDetails = $github->getLatestCommit($owner, $repositoryName, $providerBranch);
} catch (\Throwable $error) {
Console::warning('Failed to get latest commit details');
Console::warning($error->getMessage());
Console::warning($error->getTraceAsString());
}
}
2023-08-10 03:53:58 +12:00
2023-06-17 02:43:37 +12:00
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceId' => $function->getId(),
'resourceType' => 'functions',
'entrypoint' => $entrypoint,
'commands' => $function->getAttribute('commands', ''),
2023-06-17 02:43:37 +12:00
'type' => 'vcs',
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
2023-08-10 03:53:58 +12:00
'providerRepositoryId' => $providerRepositoryId,
'repositoryId' => $function->getAttribute('repositoryId', ''),
'repositoryInternalId' => $function->getAttribute('repositoryInternalId', ''),
2023-08-10 05:35:23 +12:00
'providerBranchUrl' => $branchUrl,
2023-08-10 03:53:58 +12:00
'providerRepositoryName' => $repositoryName,
'providerRepositoryOwner' => $owner,
'providerRepositoryUrl' => $repositoryUrl,
'providerCommitHash' => $commitDetails['commitHash'] ?? '',
2023-08-10 05:35:23 +12:00
'providerCommitAuthorUrl' => $authorUrl,
2023-08-10 03:53:58 +12:00
'providerCommitAuthor' => $commitDetails['commitAuthor'] ?? '',
'providerCommitMessage' => $commitDetails['commitMessage'] ?? '',
'providerCommitUrl' => $commitDetails['commitUrl'] ?? '',
'providerBranch' => $providerBranch,
'providerRootDirectory' => $function->getAttribute('providerRootDirectory', ''),
2023-06-17 02:43:37 +12:00
'search' => implode(' ', [$deploymentId, $entrypoint]),
'activate' => true,
]));
2023-08-11 19:52:08 +12:00
2023-06-17 02:43:37 +12:00
$buildEvent = new Build();
$buildEvent
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setTemplate($template)
2023-06-17 02:43:37 +12:00
->setProject($project)
->trigger();
};
2020-07-13 09:18:52 +12:00
App::post('/v1/functions')
->groups(['api', 'functions'])
2020-05-05 02:35:01 +12:00
->desc('Create Function')
->label('scope', 'functions.write')
->label('event', 'functions.[functionId].create')
2022-09-05 20:00:08 +12:00
->label('audits.event', 'function.create')
2022-08-10 02:38:54 +12:00
->label('audits.resource', 'function/{response.$id}')
2021-04-16 19:22:17 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2020-05-05 02:35:01 +12:00
->label('sdk.namespace', 'functions')
->label('sdk.method', 'create')
2020-05-06 05:30:12 +12:00
->label('sdk.description', '/docs/references/functions/create-function.md')
2020-11-12 10:02:24 +13:00
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FUNCTION)
2023-01-21 11:22:16 +13:00
->param('functionId', '', new CustomId(), 'Function ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
2020-09-11 02:40:14 +12:00
->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.')
->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution roles. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true)
->param('events', [], new ArrayList(new ValidatorEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true)
2020-09-11 02:40:14 +12:00
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
2022-01-21 21:05:41 +13:00
->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Function maximum execution time in seconds.', true)
->param('enabled', true, new Boolean(), 'Is function enabled?', true)
2023-02-24 22:24:20 +13:00
->param('logging', true, new Boolean(), 'Do executions get logged?', true)
2023-08-08 03:37:36 +12:00
->param('entrypoint', '', new Text(1028), 'Entrypoint File.')
2023-08-18 19:14:13 +12:00
->param('commands', '', new Text(8192, 0), 'Build Commands.', true)
2023-08-08 03:37:36 +12:00
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for vcs deployment.', true)
->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the function', true)
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function', true)
->param('providerSilentMode', false, new Boolean(), 'Is VCS connection in silent mode for the repo linked to the function?', true)
2023-08-06 22:30:38 +12:00
->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo', true)
2023-08-08 03:37:36 +12:00
->param('templateRepository', '', new Text(128, 0), 'Repository name of the template', true)
->param('templateOwner', '', new Text(128, 0), 'Owner name of the template', true)
->param('templateRootDirectory', '', new Text(128, 0), 'Path to function code in the template repo', true)
->param('templateBranch', '', new Text(128, 0), 'Branch of template repo with the code', true)
->inject('request')
2020-12-27 04:20:08 +13:00
->inject('response')
->inject('dbForProject')
2022-09-23 18:12:17 +12:00
->inject('project')
->inject('user')
->inject('events')
2022-11-07 10:41:33 +13:00
->inject('dbForConsole')
2023-08-10 03:53:58 +12:00
->inject('gitHub')
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateBranch, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $eventsInstance, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
2022-08-15 02:22:38 +12:00
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
2023-06-18 00:12:21 +12:00
// build from template
$template = new Document([]);
2023-08-18 18:55:44 +12:00
if (
!empty($templateRepository)
&& !empty($templateOwner)
&& !empty($templateRootDirectory)
&& !empty($templateBranch)
) {
$template->setAttribute('repositoryName', $templateRepository)
->setAttribute('ownerName', $templateOwner)
->setAttribute('rootDirectory', $templateRootDirectory)
->setAttribute('branch', $templateBranch);
2023-06-18 00:12:21 +12:00
}
2023-08-12 01:34:57 +12:00
$installation = $dbForConsole->getDocument('installations', $installationId);
if (!empty($installationId) && $installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
if (!empty($providerRepositoryId) && (empty($installationId) || empty($providerBranch))) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'When connecting to VCS (Version Control System), you need to provide "installationId" and "providerBranch".');
}
2023-07-28 19:56:07 +12:00
$function = $dbForProject->createDocument('functions', new Document([
'$id' => $functionId,
'execute' => $execute,
'enabled' => $enabled,
'live' => true,
'logging' => $logging,
'name' => $name,
'runtime' => $runtime,
'deploymentInternalId' => '',
'deployment' => '',
'events' => $events,
'schedule' => $schedule,
2023-02-06 09:07:46 +13:00
'scheduleInternalId' => '',
2023-08-22 05:43:03 +12:00
'scheduleId' => '',
2023-07-28 19:56:07 +12:00
'timeout' => $timeout,
'entrypoint' => $entrypoint,
'commands' => $commands,
'search' => implode(' ', [$functionId, $name, $runtime]),
'version' => 'v3',
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
'repositoryId' => '',
'repositoryInternalId' => '',
'providerBranch' => $providerBranch,
'providerRootDirectory' => $providerRootDirectory,
'providerSilentMode' => $providerSilentMode,
2023-07-28 19:56:07 +12:00
]));
2023-08-22 05:43:03 +12:00
$schedule = Authorization::skip(
fn () => $dbForConsole->createDocument('schedules', new Document([
'region' => App::getEnv('_APP_REGION', 'default'), // Todo replace with projects region
'resourceType' => 'function',
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $function->getAttribute('schedule'),
'active' => false,
]))
);
$function->setAttribute('scheduleId', $schedule->getId());
$function->setAttribute('scheduleInternalId', $schedule->getInternalId());
// Git connect logic
if (!empty($providerRepositoryId)) {
2023-07-31 07:10:25 +12:00
$repository = $dbForConsole->createDocument('repositories', new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
2023-07-28 19:56:07 +12:00
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
2023-06-28 20:48:10 +12:00
'resourceType' => 'function',
'providerPullRequestIds' => []
]));
2023-08-22 05:43:03 +12:00
$function->setAttribute('repositoryId', $repository->getId());
$function->setAttribute('repositoryInternalId', $repository->getInternalId());
}
2023-08-22 05:43:03 +12:00
$function = $dbForProject->updateDocument('functions', $function->getId(), $function);
2022-11-07 10:41:33 +13:00
// Redeploy vcs logic
if (!empty($providerRepositoryId)) {
2023-08-10 03:53:58 +12:00
$redeployVcs($request, $function, $project, $installation, $dbForProject, $template, $github);
}
2023-07-25 01:12:36 +12:00
$functionsDomain = App::getEnv('_APP_DOMAIN_FUNCTIONS', '');
if (!empty($functionsDomain)) {
2023-03-09 07:30:01 +13:00
$ruleId = ID::unique();
$routeSubdomain = ID::unique();
$domain = "{$routeSubdomain}.{$functionsDomain}";
2023-03-11 01:20:24 +13:00
$rule = Authorization::skip(
2023-03-29 02:21:42 +13:00
fn () => $dbForConsole->createDocument('rules', new Document([
2023-03-09 07:30:01 +13:00
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'resourceType' => 'function',
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
2023-03-09 08:50:51 +13:00
'status' => 'verified',
2023-03-09 07:30:01 +13:00
'certificateId' => '',
]))
);
2023-03-11 01:20:24 +13:00
/** Trigger Webhook */
$ruleModel = new Rule();
$ruleCreate = new Event(Event::WEBHOOK_QUEUE_NAME, Event::WEBHOOK_CLASS_NAME);
$ruleCreate
->setProject($project)
->setEvent('rules.[ruleId].create')
->setParam('ruleId', $rule->getId())
->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())))
->trigger();
/** Trigger Functions */
$ruleCreate
->setClass(Event::FUNCTIONS_CLASS_NAME)
->setQueue(Event::FUNCTIONS_QUEUE_NAME)
->trigger();
/** Trigger realtime event */
$allEvents = Event::generateEvents('rules.[ruleId].create', [
'ruleId' => $rule->getId(),
]);
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $rule,
project: $project
);
Realtime::send(
projectId: 'console',
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
Realtime::send(
projectId: $project->getId(),
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
2023-03-09 07:30:01 +13:00
}
2023-02-23 04:07:34 +13:00
$eventsInstance->setParam('functionId', $function->getId());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($function, Response::MODEL_FUNCTION);
2020-12-27 04:20:08 +13:00
});
2020-07-13 09:18:52 +12:00
App::get('/v1/functions')
->groups(['api', 'functions'])
2020-05-06 05:30:12 +12:00
->desc('List Functions')
->label('scope', 'functions.read')
2021-04-16 19:22:17 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2020-05-06 05:30:12 +12:00
->label('sdk.namespace', 'functions')
->label('sdk.method', 'list')
->label('sdk.description', '/docs/references/functions/list-functions.md')
2020-11-12 10:02:24 +13:00
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FUNCTION_LIST)
2023-03-30 08:38:39 +13:00
->param('queries', [], new Functions(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Functions::ALLOWED_ATTRIBUTES), true)
2020-09-11 02:40:14 +12:00
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
2020-12-27 04:20:08 +13:00
->inject('response')
->inject('dbForProject')
2023-03-09 07:30:01 +13:00
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
2020-07-13 09:18:52 +12:00
2022-08-23 21:02:06 +12:00
$queries = Query::parseQueries($queries);
2021-08-07 00:36:05 +12:00
2022-08-12 11:53:52 +12:00
if (!empty($search)) {
2022-08-23 21:02:06 +12:00
$queries[] = Query::search('search', $search);
2021-08-07 00:36:05 +12:00
}
2022-08-23 21:02:06 +12:00
// Get cursor document if there was a cursor query
2023-05-01 21:18:50 +12:00
$cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]);
$cursor = reset($cursor);
2022-08-30 23:55:23 +12:00
if ($cursor) {
2022-08-23 21:02:06 +12:00
/** @var Query $cursor */
$functionId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('functions', $functionId);
2021-08-19 01:42:03 +12:00
2022-08-12 11:53:52 +12:00
if ($cursorDocument->isEmpty()) {
2022-08-23 21:02:06 +12:00
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Function '{$functionId}' for the 'cursor' value not found.");
2022-08-12 11:53:52 +12:00
}
2022-08-23 21:02:06 +12:00
$cursor->setValue($cursorDocument);
2021-08-19 01:42:03 +12:00
}
2020-07-13 09:18:52 +12:00
2022-08-23 21:02:06 +12:00
$filterQueries = Query::groupByType($queries)['filters'];
2020-10-31 08:53:27 +13:00
$response->dynamic(new Document([
2023-03-09 07:30:01 +13:00
'functions' => $dbForProject->find('functions', $queries),
2022-08-12 11:53:52 +12:00
'total' => $dbForProject->count('functions', $filterQueries, APP_LIMIT_COUNT),
2020-10-31 08:53:27 +13:00
]), Response::MODEL_FUNCTION_LIST);
2020-12-27 04:20:08 +13:00
});
2020-07-13 09:18:52 +12:00
2021-12-10 02:02:12 +13:00
App::get('/v1/functions/runtimes')
->groups(['api', 'functions'])
2022-03-03 01:21:03 +13:00
->desc('List runtimes')
2021-12-10 02:02:12 +13:00
->label('scope', 'functions.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'listRuntimes')
->label('sdk.description', '/docs/references/functions/list-runtimes.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_RUNTIME_LIST)
->inject('response')
2022-05-25 02:28:27 +12:00
->action(function (Response $response) {
2021-12-10 02:02:12 +13:00
$runtimes = Config::getParam('runtimes');
$runtimes = array_map(function ($key) use ($runtimes) {
$runtimes[$key]['$id'] = $key;
return $runtimes[$key];
}, array_keys($runtimes));
$response->dynamic(new Document([
2022-02-27 22:57:09 +13:00
'total' => count($runtimes),
2021-12-10 02:02:12 +13:00
'runtimes' => $runtimes
]), Response::MODEL_RUNTIME_LIST);
});
2020-07-13 09:18:52 +12:00
App::get('/v1/functions/:functionId')
->groups(['api', 'functions'])
2020-05-06 05:30:12 +12:00
->desc('Get Function')
->label('scope', 'functions.read')
2021-04-16 19:22:17 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2020-05-06 05:30:12 +12:00
->label('sdk.namespace', 'functions')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/functions/get-function.md')
2020-11-12 10:02:24 +13:00
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FUNCTION)
->param('functionId', '', new UID(), 'Function ID.')
2020-12-27 04:20:08 +13:00
->inject('response')
->inject('dbForProject')
2023-03-09 07:30:01 +13:00
->action(function (string $functionId, Response $response, Database $dbForProject) {
$function = $dbForProject->getDocument('functions', $functionId);
2020-05-06 05:30:12 +12:00
2021-06-21 01:59:36 +12:00
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-05-06 05:30:12 +12:00
}
2020-10-31 08:53:27 +13:00
$response->dynamic($function, Response::MODEL_FUNCTION);
2020-12-27 04:20:08 +13:00
});
2020-07-13 09:18:52 +12:00
2020-10-31 08:53:27 +13:00
App::get('/v1/functions/:functionId/usage')
->desc('Get Function Usage')
->groups(['api', 'functions', 'usage'])
2020-10-31 08:53:27 +13:00
->label('scope', 'functions.read')
2021-04-16 19:22:17 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
2020-10-31 08:53:27 +13:00
->label('sdk.namespace', 'functions')
2022-07-17 22:30:58 +12:00
->label('sdk.method', 'getFunctionUsage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_FUNCTIONS)
->param('functionId', '', new UID(), 'Function ID.')
2020-10-31 08:53:27 +13:00
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d']), 'Date range.', true)
2020-12-27 04:20:08 +13:00
->inject('response')
->inject('dbForProject')
2022-05-25 02:28:27 +12:00
->action(function (string $functionId, string $range, Response $response, Database $dbForProject) {
2020-10-31 08:53:27 +13:00
$function = $dbForProject->getDocument('functions', $functionId);
2020-10-31 08:53:27 +13:00
2021-06-21 01:59:36 +12:00
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-10-31 08:53:27 +13:00
}
2023-02-03 00:55:23 +13:00
$periods = Config::getParam('usage', []);
$stats = $usage = [];
$days = $periods[$range];
$metrics = [
2023-03-10 04:49:03 +13:00
str_replace(['{resourceType}', '{resourceInternalId}'], ['functions', $function->getInternalId()], METRIC_FUNCTION_ID_DEPLOYMENTS),
str_replace(['{resourceType}', '{resourceInternalId}'], ['functions', $function->getInternalId()], METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE),
2023-02-03 07:16:01 +13:00
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS),
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_STORAGE),
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE),
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS),
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE),
2023-02-03 00:55:23 +13:00
];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $metric) {
$limit = $days['limit'];
$period = $days['period'];
$results = $dbForProject->find('stats', [
Query::equal('period', [$period]),
Query::equal('metric', [$metric]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$stats[$metric] = [];
foreach ($results as $result) {
$stats[$metric][$result->getAttribute('time')] = [
'value' => $result->getAttribute('value'),
];
}
2023-02-03 00:55:23 +13:00
}
});
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
};
2023-08-10 06:00:25 +12:00
foreach ($metrics as $metric) {
$usage[$metric] = [];
$leap = time() - ($days['limit'] * $days['factor']);
while ($leap < time()) {
$leap += $days['factor'];
$formatDate = date($format, $leap);
$usage[$metric][] = [
'value' => $stats[$metric][$formatDate]['value'] ?? 0,
'date' => $formatDate,
];
2020-10-31 08:53:27 +13:00
}
2023-08-10 06:00:25 +12:00
}
2023-02-03 00:55:23 +13:00
$response->dynamic(new Document([
'range' => $range,
2023-02-03 07:16:01 +13:00
'deploymentsTotal' => $usage[$metrics[0]],
2023-02-03 00:55:23 +13:00
'deploymentsStorage' => $usage[$metrics[1]],
2023-02-03 07:16:01 +13:00
'buildsTotal' => $usage[$metrics[2]],
2023-02-03 00:55:23 +13:00
'buildsStorage' => $usage[$metrics[3]],
2023-02-03 07:16:01 +13:00
'buildsTime' => $usage[$metrics[4]],
'executionsTotal' => $usage[$metrics[5]],
'executionsTime' => $usage[$metrics[6]],
2023-02-03 00:55:23 +13:00
]), Response::MODEL_USAGE_FUNCTION);
2022-07-17 22:30:58 +12:00
});
App::get('/v1/functions/usage')
->desc('Get Functions Usage')
->groups(['api', 'functions', 'usage'])
2022-07-17 22:30:58 +12:00
->label('scope', 'functions.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'getUsage')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USAGE_FUNCTIONS)
->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d']), 'Date range.', true)
->inject('response')
->inject('dbForProject')
2022-07-17 22:39:44 +12:00
->action(function (string $range, Response $response, Database $dbForProject) {
2022-07-17 22:30:58 +12:00
2023-02-03 00:55:23 +13:00
$periods = Config::getParam('usage', []);
$stats = $usage = [];
$days = $periods[$range];
$metrics = [
2023-02-03 07:16:01 +13:00
METRIC_FUNCTIONS,
METRIC_DEPLOYMENTS,
METRIC_DEPLOYMENTS_STORAGE,
METRIC_BUILDS,
METRIC_BUILDS_STORAGE,
METRIC_BUILDS_COMPUTE,
METRIC_EXECUTIONS,
METRIC_EXECUTIONS_COMPUTE,
2023-02-03 00:55:23 +13:00
];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $metric) {
$limit = $days['limit'];
$period = $days['period'];
$results = $dbForProject->find('stats', [
Query::equal('period', [$period]),
Query::equal('metric', [$metric]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$stats[$metric] = [];
foreach ($results as $result) {
$stats[$metric][$result->getAttribute('time')] = [
'value' => $result->getAttribute('value'),
];
}
2023-02-03 00:55:23 +13:00
}
});
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
};
2023-08-10 06:00:25 +12:00
foreach ($metrics as $metric) {
$usage[$metric] = [];
$leap = time() - ($days['limit'] * $days['factor']);
while ($leap < time()) {
$leap += $days['factor'];
$formatDate = date($format, $leap);
$usage[$metric][] = [
'value' => $stats[$metric][$formatDate]['value'] ?? 0,
'date' => $formatDate,
];
2023-08-10 03:53:58 +12:00
}
2023-08-10 06:00:25 +12:00
}
2023-02-03 00:55:23 +13:00
$response->dynamic(new Document([
'range' => $range,
2023-02-03 07:16:01 +13:00
'functionsTotal' => $usage[$metrics[0]],
'deploymentsTotal' => $usage[$metrics[1]],
2023-02-03 00:55:23 +13:00
'deploymentsStorage' => $usage[$metrics[2]],
2023-02-03 07:16:01 +13:00
'buildsTotal' => $usage[$metrics[3]],
2023-02-03 00:55:23 +13:00
'buildsStorage' => $usage[$metrics[4]],
2023-02-03 07:16:01 +13:00
'buildsTime' => $usage[$metrics[5]],
'executionsTotal' => $usage[$metrics[6]],
'executionsTime' => $usage[$metrics[7]],
2023-02-03 00:55:23 +13:00
]), Response::MODEL_USAGE_FUNCTIONS);
2020-12-27 04:20:08 +13:00
});
2020-10-31 08:53:27 +13:00
2020-07-13 09:18:52 +12:00
App::put('/v1/functions/:functionId')
->groups(['api', 'functions'])
2020-05-06 05:30:12 +12:00
->desc('Update Function')
->label('scope', 'functions.write')
->label('event', 'functions.[functionId].update')
2022-09-05 20:00:08 +12:00
->label('audits.event', 'function.update')
2022-08-10 02:38:54 +12:00
->label('audits.resource', 'function/{response.$id}')
2021-04-16 19:22:17 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2020-05-06 05:30:12 +12:00
->label('sdk.namespace', 'functions')
->label('sdk.method', 'update')
->label('sdk.description', '/docs/references/functions/update-function.md')
2020-11-12 10:02:24 +13:00
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FUNCTION)
->param('functionId', '', new UID(), 'Function ID.')
2020-09-11 02:40:14 +12:00
->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
2023-08-08 21:28:25 +12:00
->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.')
2023-03-02 01:00:36 +13:00
->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution roles. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true)
->param('events', [], new ArrayList(new ValidatorEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true)
2020-09-11 02:40:14 +12:00
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
2022-01-21 21:05:41 +13:00
->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Maximum execution time in seconds.', true)
->param('enabled', true, new Boolean(), 'Is function enabled?', true)
2023-02-24 22:24:20 +13:00
->param('logging', true, new Boolean(), 'Do executions get logged?', true)
2023-08-08 03:37:36 +12:00
->param('entrypoint', '', new Text(1028), 'Entrypoint File.')
2023-08-18 19:14:13 +12:00
->param('commands', '', new Text(8192, 0), 'Build Commands.', true)
2023-08-08 03:37:36 +12:00
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for vcs deployment.', true)
->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the function', true)
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function', true)
->param('providerSilentMode', false, new Boolean(), 'Is VCS connection in silent mode for the repo linked to the function?', true)
2023-08-06 22:30:38 +12:00
->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo', true)
->inject('request')
2020-12-27 04:20:08 +13:00
->inject('response')
->inject('dbForProject')
2021-01-17 12:38:13 +13:00
->inject('project')
->inject('events')
2022-11-04 18:12:08 +13:00
->inject('dbForConsole')
2023-08-10 03:53:58 +12:00
->inject('gitHub')
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, Request $request, Response $response, Database $dbForProject, Document $project, Event $eventsInstance, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
// TODO: If only branch changes, re-deploy
2021-01-17 12:38:13 +13:00
$function = $dbForProject->getDocument('functions', $functionId);
2020-07-13 09:18:52 +12:00
2021-06-21 01:59:36 +12:00
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-07-13 09:18:52 +12:00
}
2023-08-12 01:34:57 +12:00
$installation = $dbForConsole->getDocument('installations', $installationId);
if (!empty($installationId) && $installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
if (!empty($providerRepositoryId) && (empty($installationId) || empty($providerBranch))) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'When connecting to VCS (Version Control System), you need to provide "installationId" and "providerBranch".');
}
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$enabled ??= $function->getAttribute('enabled', true);
$repositoryId = $function->getAttribute('repositoryId', '');
$repositoryInternalId = $function->getAttribute('repositoryInternalId', '');
$isConnected = !empty($function->getAttribute('providerRepositoryId', ''));
// Git disconnect logic
if ($isConnected && empty($providerRepositoryId)) {
2023-07-31 07:10:25 +12:00
$repositories = $dbForConsole->find('repositories', [
2023-07-01 18:46:21 +12:00
Query::equal('projectInternalId', [$project->getInternalId()]),
2023-07-28 19:56:07 +12:00
Query::equal('resourceInternalId', [$function->getInternalId()]),
2023-06-10 00:36:33 +12:00
Query::equal('resourceType', ['function']),
Query::limit(100),
]);
foreach ($repositories as $repository) {
2023-07-31 07:10:25 +12:00
$dbForConsole->deleteDocument('repositories', $repository->getId());
2023-06-10 00:36:33 +12:00
}
$providerRepositoryId = '';
$installationId = '';
$providerBranch = '';
$providerRootDirectory = '';
$providerSilentMode = true;
$repositoryId = '';
$repositoryInternalId = '';
}
// Git connect logic
if (!$isConnected && !empty($providerRepositoryId)) {
$teamId = $project->getAttribute('teamId', '');
2023-07-31 07:10:25 +12:00
$repository = $dbForConsole->createDocument('repositories', new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::team(ID::custom($teamId))),
Permission::update(Role::team(ID::custom($teamId), 'owner')),
Permission::update(Role::team(ID::custom($teamId), 'developer')),
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
],
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
2023-07-28 19:56:07 +12:00
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
2023-06-28 20:48:10 +12:00
'resourceType' => 'function',
'providerPullRequestIds' => []
]));
$repositoryId = $repository->getId();
$repositoryInternalId = $repository->getInternalId();
}
$live = true;
if (
2023-08-20 21:42:17 +12:00
$function->getAttribute('name') !== $name ||
$function->getAttribute('entrypoint') !== $entrypoint ||
$function->getAttribute('commands') !== $commands ||
2023-08-08 21:46:43 +12:00
$function->getAttribute('providerRootDirectory') !== $providerRootDirectory ||
2023-08-08 21:28:25 +12:00
$function->getAttribute('runtime') !== $runtime
) {
$live = false;
}
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
2021-05-05 09:25:17 +12:00
'execute' => $execute,
2020-07-13 09:18:52 +12:00
'name' => $name,
2023-08-08 21:28:25 +12:00
'runtime' => $runtime,
2020-07-13 09:18:52 +12:00
'events' => $events,
'schedule' => $schedule,
2021-01-17 12:38:13 +13:00
'timeout' => $timeout,
'enabled' => $enabled,
'live' => $live,
2023-02-24 22:24:20 +13:00
'logging' => $logging,
2023-03-16 23:07:15 +13:00
'entrypoint' => $entrypoint,
'commands' => $commands,
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
'repositoryId' => $repositoryId,
'repositoryInternalId' => $repositoryInternalId,
'providerBranch' => $providerBranch,
'providerRootDirectory' => $providerRootDirectory,
'providerSilentMode' => $providerSilentMode,
2023-08-08 21:28:25 +12:00
'search' => implode(' ', [$functionId, $name, $runtime]),
2021-05-05 09:25:17 +12:00
])));
2020-05-06 05:30:12 +12:00
2023-06-17 02:43:37 +12:00
// Redeploy logic
if (!$isConnected && !empty($providerRepositoryId)) {
2023-08-10 03:53:58 +12:00
$redeployVcs($request, $function, $project, $installation, $dbForProject, new Document(), $github);
2023-06-17 02:43:37 +12:00
}
2023-07-28 19:56:07 +12:00
// Inform scheduler if function is still active
2022-11-16 23:33:11 +13:00
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
2022-11-16 05:14:52 +13:00
$schedule
2023-03-19 22:43:57 +13:00
->setAttribute('resourceUpdatedAt', DateTime::now())
2022-11-08 04:42:08 +13:00
->setAttribute('schedule', $function->getAttribute('schedule'))
2022-11-09 03:00:39 +13:00
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
2022-11-16 21:41:57 +13:00
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
2022-11-07 23:37:07 +13:00
$eventsInstance->setParam('functionId', $function->getId());
2020-10-31 08:53:27 +13:00
$response->dynamic($function, Response::MODEL_FUNCTION);
2020-12-27 04:20:08 +13:00
});
2020-07-13 09:18:52 +12:00
2023-08-21 22:21:18 +12:00
App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
->groups(['api', 'functions'])
->desc('Download Deployment')
->label('scope', 'functions.read')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'downloadDeployment')
->label('sdk.description', '/docs/references/functions/download-deployment.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', '*/*')
->label('sdk.methodType', 'location')
->param('functionId', '', new UID(), 'Function ID.')
->param('deploymentId', '', new UID(), 'Deployment ID.')
->inject('response')
->inject('request')
->inject('dbForProject')
->inject('deviceFunctions')
->action(function (string $functionId, string $deploymentId, Response $response, Request $request, Database $dbForProject, Device $deviceFunctions) {
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$path = $deployment->getAttribute('path', '');
if (!$deviceFunctions->exists($path)) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$response
->setContentType('application/gzip')
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->addHeader('X-Peak', \memory_get_peak_usage())
->addHeader('Content-Disposition', 'attachment; filename="'.$deploymentId.'.tar.gz"')
;
$size = $deviceFunctions->getFileSize($path);
$rangeHeader = $request->getHeader('range');
if (!empty($rangeHeader)) {
$start = $request->getRangeStart();
$end = $request->getRangeEnd();
$unit = $request->getRangeUnit();
if ($end === null) {
$end = min(($start + MAX_OUTPUT_CHUNK_SIZE - 1), ($size - 1));
}
if ($unit !== 'bytes' || $start >= $end || $end >= $size) {
throw new Exception(Exception::STORAGE_INVALID_RANGE);
}
$response
->addHeader('Accept-Ranges', 'bytes')
->addHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $size)
->addHeader('Content-Length', $end - $start + 1)
->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT);
$response->send($deviceFunctions->read($path, $start, ($end - $start + 1)));
}
if ($size > APP_STORAGE_READ_BUFFER) {
$response->addHeader('Content-Length', $deviceFunctions->getFileSize($path));
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
$response->chunk(
$deviceFunctions->read(
$path,
($i * MAX_OUTPUT_CHUNK_SIZE),
min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))
),
(($i + 1) * MAX_OUTPUT_CHUNK_SIZE) >= $size
);
}
} else {
$response->send($deviceFunctions->read($path));
}
});
App::patch('/v1/functions/:functionId/deployments/:deploymentId')
2020-07-13 09:18:52 +12:00
->groups(['api', 'functions'])
->desc('Update Function Deployment')
2020-05-07 05:35:56 +12:00
->label('scope', 'functions.write')
->label('event', 'functions.[functionId].deployments.[deploymentId].update')
2022-09-05 20:00:08 +12:00
->label('audits.event', 'deployment.update')
2022-08-13 19:34:03 +12:00
->label('audits.resource', 'function/{request.functionId}')
2021-04-16 19:22:17 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2020-05-07 05:35:56 +12:00
->label('sdk.namespace', 'functions')
->label('sdk.method', 'updateDeployment')
->label('sdk.description', '/docs/references/functions/update-function-deployment.md')
2020-11-12 10:02:24 +13:00
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_FUNCTION)
->param('functionId', '', new UID(), 'Function ID.')
->param('deploymentId', '', new UID(), 'Deployment ID.')
2020-12-27 04:20:08 +13:00
->inject('response')
->inject('dbForProject')
2021-01-17 12:38:13 +13:00
->inject('project')
->inject('events')
2022-11-07 10:41:33 +13:00
->inject('dbForConsole')
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $events, Database $dbForConsole) {
2021-01-17 12:38:13 +13:00
2022-01-06 22:45:56 +13:00
$function = $dbForProject->getDocument('functions', $functionId);
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
2022-01-27 12:19:02 +13:00
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
2021-01-17 12:38:13 +13:00
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
if ($build->isEmpty()) {
throw new Exception(Exception::BUILD_NOT_FOUND);
2020-05-07 05:35:56 +12:00
}
if ($build->getAttribute('status') !== 'ready') {
throw new Exception(Exception::BUILD_NOT_READY);
}
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
2022-12-26 01:07:54 +13:00
'deploymentInternalId' => $deployment->getInternalId(),
2023-03-15 03:10:36 +13:00
'deployment' => $deployment->getId(),
])));
2023-07-28 19:56:07 +12:00
// Inform scheduler if function is still active
2022-11-16 23:33:11 +13:00
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
2023-03-19 22:43:57 +13:00
$schedule
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $function->getAttribute('schedule'))
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
2022-11-16 03:33:43 +13:00
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
2022-11-07 10:41:33 +13:00
$events
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId());
$response->dynamic($function, Response::MODEL_FUNCTION);
2020-12-27 04:20:08 +13:00
});
2020-07-13 09:18:52 +12:00
App::delete('/v1/functions/:functionId')
->groups(['api', 'functions'])
2020-05-06 05:30:12 +12:00
->desc('Delete Function')
->label('scope', 'functions.write')
->label('event', 'functions.[functionId].delete')
2022-09-05 20:00:08 +12:00
->label('audits.event', 'function.delete')
2022-08-10 02:38:54 +12:00
->label('audits.resource', 'function/{request.functionId}')
2021-04-16 19:22:17 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2020-05-06 05:30:12 +12:00
->label('sdk.namespace', 'functions')
->label('sdk.method', 'delete')
->label('sdk.description', '/docs/references/functions/delete-function.md')
2020-11-12 10:02:24 +13:00
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('functionId', '', new UID(), 'Function ID.')
2020-12-27 04:20:08 +13:00
->inject('response')
->inject('dbForProject')
2020-12-27 04:20:08 +13:00
->inject('deletes')
->inject('events')
2022-11-04 18:12:08 +13:00
->inject('project')
->inject('dbForConsole')
->action(function (string $functionId, Response $response, Database $dbForProject, Delete $deletes, Event $events, Document $project, Database $dbForConsole) {
2020-10-31 08:53:27 +13:00
$function = $dbForProject->getDocument('functions', $functionId);
2020-05-06 05:30:12 +12:00
2021-06-21 01:59:36 +12:00
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-07-13 09:18:52 +12:00
}
2020-05-07 00:12:52 +12:00
if (!$dbForProject->deleteDocument('functions', $function->getId())) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove function from DB');
2020-05-05 02:35:01 +12:00
}
2020-05-06 05:30:12 +12:00
2023-07-28 19:56:07 +12:00
// Inform scheduler to no longer run function
2022-11-16 23:33:11 +13:00
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
2022-11-16 05:14:52 +13:00
$schedule
2022-11-07 10:41:33 +13:00
->setAttribute('resourceUpdatedAt', DateTime::now())
2023-03-29 02:21:42 +13:00
->setAttribute('active', false);
2022-11-16 03:33:43 +13:00
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
2022-11-04 18:12:08 +13:00
2020-10-31 08:53:27 +13:00
$deletes
2022-04-18 08:34:32 +12:00
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($function);
$events->setParam('functionId', $function->getId());
2020-10-31 08:53:27 +13:00
2020-07-13 09:18:52 +12:00
$response->noContent();
2020-12-27 04:20:08 +13:00
});
2020-07-13 09:18:52 +12:00
2022-01-25 12:05:17 +13:00
App::post('/v1/functions/:functionId/deployments')
2020-07-13 09:18:52 +12:00
->groups(['api', 'functions'])
2022-01-25 12:05:17 +13:00
->desc('Create Deployment')
2020-05-06 05:30:12 +12:00
->label('scope', 'functions.write')
->label('event', 'functions.[functionId].deployments.[deploymentId].create')
2022-09-05 20:00:08 +12:00
->label('audits.event', 'deployment.create')
2022-08-13 19:34:03 +12:00
->label('audits.resource', 'function/{request.functionId}')
2021-04-16 19:22:17 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2020-05-06 05:30:12 +12:00
->label('sdk.namespace', 'functions')
2022-01-25 12:05:17 +13:00
->label('sdk.method', 'createDeployment')
->label('sdk.description', '/docs/references/functions/create-deployment.md')
2021-01-30 03:29:53 +13:00
->label('sdk.packaging', true)
2020-11-12 10:02:24 +13:00
->label('sdk.request.type', 'multipart/form-data')
2022-07-18 21:43:51 +12:00
->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED)
2020-11-12 10:02:24 +13:00
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
2022-01-25 12:05:17 +13:00
->label('sdk.response.model', Response::MODEL_DEPLOYMENT)
->param('functionId', '', new UID(), 'Function ID.')
2023-03-02 01:00:36 +13:00
->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.', skipValidation: true)
->param('activate', false, new Boolean(true), 'Automatically activate the deployment when it is finished building.')
2020-12-27 04:20:08 +13:00
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('events')
->inject('project')
->inject('deviceFunctions')
->inject('deviceLocal')
->action(function (string $functionId, mixed $code, mixed $activate, Request $request, Response $response, Database $dbForProject, Event $events, Document $project, Device $deviceFunctions, Device $deviceLocal) {
$activate = filter_var($activate, FILTER_VALIDATE_BOOLEAN);
$function = $dbForProject->getDocument('functions', $functionId);
2020-07-13 09:18:52 +12:00
2021-06-21 01:59:36 +12:00
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-07-13 09:18:52 +12:00
}
2020-07-15 08:33:52 +12:00
2023-05-12 07:57:31 +12:00
$entrypoint = $function->getAttribute('entrypoint', '');
$commands = $function->getAttribute('commands', '');
2023-03-16 23:07:15 +13:00
2023-03-29 02:21:42 +13:00
if (empty($entrypoint)) {
2023-03-16 23:07:15 +13:00
throw new Exception(Exception::FUNCTION_ENTRYPOINT_MISSING);
}
2021-02-01 08:11:03 +13:00
$file = $request->getFiles('code');
2022-12-13 19:49:20 +13:00
// GraphQL multipart spec adds files with index keys
if (empty($file)) {
$file = $request->getFiles(0);
}
2020-07-15 08:33:52 +12:00
if (empty($file)) {
throw new Exception(Exception::STORAGE_FILE_EMPTY, 'No file sent');
2020-07-15 08:33:52 +12:00
}
2022-12-13 19:49:20 +13:00
$fileExt = new FileExt([FileExt::TYPE_GZIP]);
$fileSizeValidator = new FileSize(App::getEnv('_APP_FUNCTIONS_SIZE_LIMIT', 0));
$upload = new Upload();
2020-07-15 08:33:52 +12:00
// Make sure we handle a single file and multiple files the same way
$fileName = (\is_array($file['name']) && isset($file['name'][0])) ? $file['name'][0] : $file['name'];
$fileTmpName = (\is_array($file['tmp_name']) && isset($file['tmp_name'][0])) ? $file['tmp_name'][0] : $file['tmp_name'];
2021-11-30 21:06:54 +13:00
$fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
2020-07-15 08:33:52 +12:00
2021-01-28 07:08:46 +13:00
if (!$fileExt->isValid($file['name'])) { // Check if file type is allowed
throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED);
2021-01-27 11:15:20 +13:00
}
2020-07-15 08:33:52 +12:00
$contentRange = $request->getHeader('content-range');
2022-08-15 02:22:38 +12:00
$deploymentId = ID::unique();
$chunk = 1;
$chunks = 1;
if (!empty($contentRange)) {
$start = $request->getContentRangeStart();
$end = $request->getContentRangeEnd();
2021-11-30 21:06:54 +13:00
$fileSize = $request->getContentRangeSize();
$deploymentId = $request->getHeader('x-appwrite-id', $deploymentId);
2023-04-27 21:29:39 +12:00
// TODO make `end >= $fileSize` in next breaking version
if (is_null($start) || is_null($end) || is_null($fileSize) || $end > $fileSize) {
throw new Exception(Exception::STORAGE_INVALID_CONTENT_RANGE);
}
2023-04-27 21:29:39 +12:00
// TODO remove the condition that checks `$end === $fileSize` in next breaking version
if ($end === $fileSize - 1 || $end === $fileSize) {
//if it's a last chunks the chunk size might differ, so we set the $chunks and $chunk to notify it's last chunk
$chunks = $chunk = -1;
} else {
// Calculate total number of chunks based on the chunk size i.e ($rangeEnd - $rangeStart)
2021-11-30 21:06:54 +13:00
$chunks = (int) ceil($fileSize / ($end + 1 - $start));
$chunk = (int) ($start / ($end + 1 - $start)) + 1;
}
}
2021-11-30 21:06:54 +13:00
if (!$fileSizeValidator->isValid($fileSize)) { // Check if file size is exceeding allowed limit
throw new Exception(Exception::STORAGE_INVALID_FILE_SIZE);
2020-07-15 08:33:52 +12:00
}
if (!$upload->isValid($fileTmpName)) {
throw new Exception(Exception::STORAGE_INVALID_FILE);
2020-07-15 08:33:52 +12:00
}
// Save to storage
$fileSize ??= $deviceLocal->getFileSize($fileTmpName);
$path = $deviceFunctions->getPath($deploymentId . '.' . \pathinfo($fileName, PATHINFO_EXTENSION));
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
$metadata = ['content_type' => $deviceLocal->getFileMimeType($fileTmpName)];
if (!$deployment->isEmpty()) {
$chunks = $deployment->getAttribute('chunksTotal', 1);
$metadata = $deployment->getAttribute('metadata', []);
if ($chunk === -1) {
2021-11-30 21:40:21 +13:00
$chunk = $chunks;
}
}
$chunksUploaded = $deviceFunctions->upload($fileTmpName, $path, $chunk, $chunks, $metadata);
if (empty($chunksUploaded)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed moving file');
2020-07-15 08:33:52 +12:00
}
2022-03-02 04:09:21 +13:00
$activate = (bool) filter_var($activate, FILTER_VALIDATE_BOOLEAN);
2022-02-26 03:27:50 +13:00
if ($chunksUploaded === $chunks) {
2022-02-26 03:27:50 +13:00
if ($activate) {
// Remove deploy for all other deployments.
$activeDeployments = $dbForProject->find('deployments', [
2022-08-12 11:53:52 +12:00
Query::equal('activate', [true]),
Query::equal('resourceId', [$functionId]),
Query::equal('resourceType', ['functions'])
2022-02-26 03:27:50 +13:00
]);
foreach ($activeDeployments as $activeDeployment) {
$activeDeployment->setAttribute('activate', false);
$dbForProject->updateDocument('deployments', $activeDeployment->getId(), $activeDeployment);
}
}
$fileSize = $deviceFunctions->getFileSize($path);
if ($deployment->isEmpty()) {
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
2022-08-14 17:21:11 +12:00
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
2022-12-26 00:06:25 +13:00
'resourceInternalId' => $function->getInternalId(),
'resourceId' => $function->getId(),
2022-02-19 04:38:48 +13:00
'resourceType' => 'functions',
2023-02-06 09:07:46 +13:00
'buildInternalId' => '',
'entrypoint' => $entrypoint,
'commands' => $commands,
'path' => $path,
2021-11-30 21:06:54 +13:00
'size' => $fileSize,
'search' => implode(' ', [$deploymentId, $entrypoint]),
'activate' => $activate,
'metadata' => $metadata,
2023-05-22 23:29:35 +12:00
'type' => 'manual'
]));
} else {
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('size', $fileSize)->setAttribute('metadata', $metadata));
}
2022-02-26 00:06:58 +13:00
2022-04-20 01:13:55 +12:00
// Start the build
$buildEvent = new Build();
$buildEvent
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setProject($project)
->trigger();
} else {
if ($deployment->isEmpty()) {
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
2022-08-14 17:21:11 +12:00
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
2022-12-26 00:06:25 +13:00
'resourceInternalId' => $function->getInternalId(),
'resourceId' => $function->getId(),
2022-02-19 04:38:48 +13:00
'resourceType' => 'functions',
2023-02-06 09:07:46 +13:00
'buildInternalId' => '',
'entrypoint' => $entrypoint,
'commands' => $commands,
'path' => $path,
2022-02-23 01:31:04 +13:00
'size' => $fileSize,
'chunksTotal' => $chunks,
'chunksUploaded' => $chunksUploaded,
'search' => implode(' ', [$deploymentId, $entrypoint]),
'activate' => $activate,
'metadata' => $metadata,
2023-05-22 23:29:35 +12:00
'type' => 'manual'
]));
} else {
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('chunksUploaded', $chunksUploaded)->setAttribute('metadata', $metadata));
}
}
2020-05-06 05:30:12 +12:00
$metadata = null;
$events
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId());
$response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($deployment, Response::MODEL_DEPLOYMENT);
2020-12-27 04:20:08 +13:00
});
2020-07-13 09:18:52 +12:00
2022-01-25 12:09:24 +13:00
App::get('/v1/functions/:functionId/deployments')
2020-07-13 09:18:52 +12:00
->groups(['api', 'functions'])
2022-01-25 12:09:24 +13:00
->desc('List Deployments')
2020-05-06 05:30:12 +12:00
->label('scope', 'functions.read')
2021-04-16 19:22:17 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2020-05-06 05:30:12 +12:00
->label('sdk.namespace', 'functions')
2022-01-25 12:09:24 +13:00
->label('sdk.method', 'listDeployments')
->label('sdk.description', '/docs/references/functions/list-deployments.md')
2020-11-12 10:02:24 +13:00
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
2022-01-25 12:09:24 +13:00
->label('sdk.response.model', Response::MODEL_DEPLOYMENT_LIST)
->param('functionId', '', new UID(), 'Function ID.')
2023-03-30 08:38:39 +13:00
->param('queries', [], new Deployments(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Deployments::ALLOWED_ATTRIBUTES), true)
2020-09-11 02:40:14 +12:00
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
2020-12-27 04:20:08 +13:00
->inject('response')
->inject('dbForProject')
2022-08-23 21:12:48 +12:00
->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject) {
2021-01-17 12:38:13 +13:00
$function = $dbForProject->getDocument('functions', $functionId);
2020-05-06 05:30:12 +12:00
2021-06-21 01:59:36 +12:00
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-07-13 09:18:52 +12:00
}
2021-05-27 22:09:14 +12:00
2022-08-23 21:12:48 +12:00
$queries = Query::parseQueries($queries);
2021-08-07 00:36:05 +12:00
2022-08-12 11:53:52 +12:00
if (!empty($search)) {
2022-08-23 21:12:48 +12:00
$queries[] = Query::search('search', $search);
2021-08-07 00:36:05 +12:00
}
2022-08-23 21:12:48 +12:00
// Set resource queries
$queries[] = Query::equal('resourceId', [$function->getId()]);
$queries[] = Query::equal('resourceType', ['functions']);
2021-08-19 01:42:03 +12:00
2022-08-23 21:12:48 +12:00
// Get cursor document if there was a cursor query
2023-05-01 21:18:50 +12:00
$cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]);
$cursor = reset($cursor);
2022-08-30 23:55:23 +12:00
if ($cursor) {
2022-08-23 21:12:48 +12:00
/** @var Query $cursor */
$deploymentId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('deployments', $deploymentId);
2021-08-19 01:42:03 +12:00
2022-08-12 11:53:52 +12:00
if ($cursorDocument->isEmpty()) {
2022-08-23 21:12:48 +12:00
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Deployment '{$deploymentId}' for the 'cursor' value not found.");
2022-08-12 11:53:52 +12:00
}
2021-08-19 01:42:03 +12:00
2022-08-23 21:12:48 +12:00
$cursor->setValue($cursorDocument);
2021-08-19 01:42:03 +12:00
}
2022-08-23 21:12:48 +12:00
$filterQueries = Query::groupByType($queries)['filters'];
2021-08-19 01:42:03 +12:00
2022-08-23 21:12:48 +12:00
$results = $dbForProject->find('deployments', $queries);
2022-08-12 11:53:52 +12:00
$total = $dbForProject->count('deployments', $filterQueries, APP_LIMIT_COUNT);
2020-07-13 09:18:52 +12:00
2022-02-01 00:29:31 +13:00
foreach ($results as $result) {
2022-02-17 00:43:21 +13:00
$build = $dbForProject->getDocument('builds', $result->getAttribute('buildId', ''));
$result->setAttribute('status', $build->getAttribute('status', 'processing'));
2023-08-06 02:50:28 +12:00
$result->setAttribute('buildLogs', $build->getAttribute('logs', ''));
2022-10-11 04:24:40 +13:00
$result->setAttribute('buildTime', $build->getAttribute('duration', 0));
$result->setAttribute('size', $result->getAttribute('size', 0) + $build->getAttribute('size', 0));
}
2020-10-31 08:53:27 +13:00
$response->dynamic(new Document([
2022-01-25 12:09:24 +13:00
'deployments' => $results,
2022-02-27 22:57:09 +13:00
'total' => $total,
2022-01-25 12:09:24 +13:00
]), Response::MODEL_DEPLOYMENT_LIST);
2020-12-27 04:20:08 +13:00
});
2020-07-13 09:18:52 +12:00
2022-01-25 12:11:33 +13:00
App::get('/v1/functions/:functionId/deployments/:deploymentId')
2020-07-13 09:18:52 +12:00
->groups(['api', 'functions'])
2022-01-25 12:11:33 +13:00
->desc('Get Deployment')
2020-05-06 05:30:12 +12:00
->label('scope', 'functions.read')
2021-04-16 19:22:17 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2020-05-06 05:30:12 +12:00
->label('sdk.namespace', 'functions')
2022-01-25 12:11:33 +13:00
->label('sdk.method', 'getDeployment')
->label('sdk.description', '/docs/references/functions/get-deployment.md')
2020-11-12 10:02:24 +13:00
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
2022-07-07 16:49:44 +12:00
->label('sdk.response.model', Response::MODEL_DEPLOYMENT)
->param('functionId', '', new UID(), 'Function ID.')
2022-01-25 12:11:33 +13:00
->param('deploymentId', '', new UID(), 'Deployment ID.')
2020-12-27 04:20:08 +13:00
->inject('response')
->inject('dbForProject')
2022-05-25 02:28:27 +12:00
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject) {
2021-01-17 12:38:13 +13:00
$function = $dbForProject->getDocument('functions', $functionId);
2020-05-06 05:30:12 +12:00
2021-06-21 01:59:36 +12:00
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-07-13 09:18:52 +12:00
}
2020-05-06 05:30:12 +12:00
2022-01-25 12:11:33 +13:00
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
2020-05-06 05:30:12 +12:00
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
2020-07-13 09:18:52 +12:00
}
2020-05-06 05:30:12 +12:00
2022-01-25 12:11:33 +13:00
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
2020-05-06 05:30:12 +12:00
}
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
$deployment->setAttribute('status', $build->getAttribute('status', 'waiting'));
2023-08-06 02:50:28 +12:00
$deployment->setAttribute('buildLogs', $build->getAttribute('logs', ''));
$deployment->setAttribute('buildTime', $build->getAttribute('duration', 0));
$deployment->setAttribute('size', $deployment->getAttribute('size', 0) + $build->getAttribute('size', 0));
2022-01-25 12:11:33 +13:00
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
2020-12-27 04:20:08 +13:00
});
2020-07-13 09:18:52 +12:00
2022-01-25 12:14:21 +13:00
App::delete('/v1/functions/:functionId/deployments/:deploymentId')
2020-07-13 09:18:52 +12:00
->groups(['api', 'functions'])
2022-01-25 12:14:21 +13:00
->desc('Delete Deployment')
2020-05-06 05:30:12 +12:00
->label('scope', 'functions.write')
->label('event', 'functions.[functionId].deployments.[deploymentId].delete')
2022-09-05 20:00:08 +12:00
->label('audits.event', 'deployment.delete')
2022-08-13 19:34:03 +12:00
->label('audits.resource', 'function/{request.functionId}')
2021-04-16 19:22:17 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2020-05-06 05:30:12 +12:00
->label('sdk.namespace', 'functions')
2022-01-25 12:14:21 +13:00
->label('sdk.method', 'deleteDeployment')
->label('sdk.description', '/docs/references/functions/delete-deployment.md')
2020-11-12 10:02:24 +13:00
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('functionId', '', new UID(), 'Function ID.')
2022-01-25 12:14:21 +13:00
->param('deploymentId', '', new UID(), 'Deployment ID.')
2020-12-27 04:20:08 +13:00
->inject('response')
->inject('dbForProject')
->inject('deletes')
->inject('events')
->inject('deviceFunctions')
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Delete $deletes, Event $events, Device $deviceFunctions) {
2021-01-17 12:38:13 +13:00
$function = $dbForProject->getDocument('functions', $functionId);
2021-06-21 01:59:36 +12:00
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-07-13 09:18:52 +12:00
}
2022-01-25 12:14:21 +13:00
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
2022-02-01 12:44:55 +13:00
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
2023-06-14 20:57:30 +12:00
if (!$dbForProject->deleteDocument('deployments', $deployment->getId())) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove deployment from DB');
}
if (!empty($deployment->getAttribute('path', ''))) {
if (!($deviceFunctions->delete($deployment->getAttribute('path', '')))) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove deployment from storage');
2020-07-19 09:48:28 +12:00
}
2020-05-06 05:30:12 +12:00
}
if ($function->getAttribute('deployment') === $deployment->getId()) { // Reset function deployment
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
2022-01-25 12:14:21 +13:00
'deployment' => '',
2022-12-26 01:07:54 +13:00
'deploymentInternalId' => '',
2021-05-05 09:25:17 +12:00
])));
2020-10-31 08:53:27 +13:00
}
$events
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId());
2020-07-19 09:48:28 +12:00
$deletes
2022-04-18 08:34:32 +12:00
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($deployment);
2020-07-13 09:18:52 +12:00
$response->noContent();
2020-12-27 04:20:08 +13:00
});
2020-07-13 09:18:52 +12:00
2022-09-29 21:42:46 +13:00
App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
->groups(['api', 'functions'])
->desc('Create Build')
->label('scope', 'functions.write')
->label('event', 'functions.[functionId].deployments.[deploymentId].update')
->label('audits.event', 'deployment.update')
->label('audits.resource', 'function/{request.functionId}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'createBuild')
->label('sdk.description', '/docs/references/functions/create-build.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('functionId', '', new UID(), 'Function ID.')
->param('deploymentId', '', new UID(), 'Deployment ID.')
->param('buildId', '', new UID(), 'Build unique ID.')
->inject('request')
2022-09-29 21:42:46 +13:00
->inject('response')
->inject('dbForProject')
->inject('dbForConsole')
2022-09-29 21:42:46 +13:00
->inject('project')
2023-08-10 03:53:58 +12:00
->inject('gitHub')
2023-08-18 09:37:52 +12:00
->inject('events')
->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Database $dbForConsole, Document $project, GitHub $github, Event $events) use ($redeployVcs) {
2022-09-29 21:42:46 +13:00
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
2023-08-19 18:15:47 +12:00
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
2022-09-29 21:42:46 +13:00
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $buildId));
if ($build->isEmpty()) {
throw new Exception(Exception::BUILD_NOT_FOUND);
}
$deploymentId = ID::unique();
2023-08-19 18:15:47 +12:00
$deployment = $dbForProject->createDocument('deployments', $deployment->setAttributes([
'$id' => $deploymentId,
2023-08-19 18:15:47 +12:00
'buildId' => '',
'buildInternalId' => '',
'entrypoint' => $function->getAttribute('entrypoint'),
'commands' => $function->getAttribute('commands', ''),
'search' => implode(' ', [$deploymentId, $function->getAttribute('entrypoint')]),
2023-08-19 18:15:47 +12:00
]));
2022-09-29 21:42:46 +13:00
2023-08-19 18:15:47 +12:00
$buildEvent = new Build();
2022-09-29 21:42:46 +13:00
2023-08-19 18:15:47 +12:00
$buildEvent
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setProject($project)
->trigger();
2022-09-29 21:42:46 +13:00
2023-08-18 09:37:52 +12:00
$events
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId());
2022-09-29 21:42:46 +13:00
$response->noContent();
});
2020-07-13 09:18:52 +12:00
App::post('/v1/functions/:functionId/executions')
->groups(['api', 'functions'])
2020-05-06 05:30:12 +12:00
->desc('Create Execution')
2020-12-30 20:26:01 +13:00
->label('scope', 'execution.write')
->label('event', 'functions.[functionId].executions.[executionId].create')
2021-04-16 19:22:17 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
2020-05-06 05:30:12 +12:00
->label('sdk.namespace', 'functions')
->label('sdk.method', 'createExecution')
->label('sdk.description', '/docs/references/functions/create-execution.md')
2020-11-12 10:02:24 +13:00
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_EXECUTION)
->param('functionId', '', new UID(), 'Function ID.')
2023-08-06 22:30:38 +12:00
->param('body', '', new Text(8192, 0), 'HTTP body of execution. Default value is empty string.', true)
->param('async', false, new Boolean(), 'Execute code in the background. Default value is false.', true)
2023-02-16 03:50:18 +13:00
->param('path', '/', new Text(2048), 'HTTP path of execution. Path can include query params. Default value is /', true)
2023-07-01 18:46:21 +12:00
->param('method', 'POST', new Whitelist(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], true), 'HTTP method of execution. Default value is GET.', true)
2023-08-18 18:55:44 +12:00
->param('headers', [], new Assoc(), 'HTTP headers of execution. Defaults to empty.', true)
2020-12-27 04:20:08 +13:00
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('user')
->inject('events')
->inject('mode')
2022-11-16 23:33:11 +13:00
->inject('queueForFunctions')
->inject('geodb')
2022-12-15 22:45:43 +13:00
->inject('queueForUsage')
2023-08-06 20:51:53 +12:00
->action(function (string $functionId, string $body, bool $async, string $path, string $method, array $headers, Response $response, Document $project, Database $dbForProject, Document $user, Event $events, string $mode, Func $queueForFunctions, Reader $geodb, Usage $queueForUsage) {
2020-07-17 09:50:37 +12:00
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
2020-07-13 09:18:52 +12:00
if ($function->isEmpty() || !$function->getAttribute('enabled')) {
if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
2020-07-13 09:18:52 +12:00
}
2020-07-17 09:50:37 +12:00
2022-02-04 14:29:40 +13:00
$runtimes = Config::getParam('runtimes', []);
2022-02-06 08:49:57 +13:00
$runtime = (isset($runtimes[$function->getAttribute('runtime', '')])) ? $runtimes[$function->getAttribute('runtime', '')] : null;
2022-02-04 14:29:40 +13:00
if (\is_null($runtime)) {
throw new Exception(Exception::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
2022-02-04 14:29:40 +13:00
}
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', '')));
2020-07-17 09:50:37 +12:00
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
2022-08-14 18:56:12 +12:00
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function');
2020-07-17 09:50:37 +12:00
}
2022-01-25 12:16:53 +13:00
if ($deployment->isEmpty()) {
2022-08-14 18:56:12 +12:00
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function');
2020-07-17 09:50:37 +12:00
}
2020-12-30 12:00:44 +13:00
/** Check if build has completed */
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
if ($build->isEmpty()) {
throw new Exception(Exception::BUILD_NOT_FOUND);
}
if ($build->getAttribute('status') !== 'ready') {
throw new Exception(Exception::BUILD_NOT_READY);
}
$validator = new Authorization('execute');
2020-12-30 12:00:44 +13:00
2021-05-05 09:25:17 +12:00
if (!$validator->isValid($function->getAttribute('execute'))) { // Check if user has write access to execute function
2022-07-27 02:56:59 +12:00
throw new Exception(Exception::USER_UNAUTHORIZED, $validator->getDescription());
2020-12-30 12:00:44 +13:00
}
2021-03-11 09:25:54 +13:00
$jwt = ''; // initialize
2021-06-21 01:59:36 +12:00
if (!$user->isEmpty()) { // If userId exists, generate a JWT for function
2021-07-23 02:49:52 +12:00
$sessions = $user->getAttribute('sessions', []);
$current = new Document();
foreach ($sessions as $session) {
/** @var Utopia\Database\Document $session */
2021-07-23 02:49:52 +12:00
if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
$current = $session;
}
}
if (!$current->isEmpty()) {
2021-03-11 09:25:54 +13:00
$jwtObj = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
$jwt = $jwtObj->encode([
2021-03-11 06:48:05 +13:00
'userId' => $user->getId(),
2021-07-23 02:49:52 +12:00
'sessionId' => $current->getId(),
2021-03-11 06:48:05 +13:00
]);
}
}
$headers['x-appwrite-trigger'] = 'http';
$headers['x-appwrite-user-id'] = $user->getId() ?? '';
$headers['x-appwrite-user-jwt'] = $jwt ?? '';
$headers['x-appwrite-country-code'] = '';
$headers['x-appwrite-continent-code'] = '';
$headers['x-appwrite-continent-eu'] = 'false';
$ip = $headers['x-real-ip'] ?? '';
if (!empty($ip)) {
$record = $geodb->get($ip);
if ($record) {
$eu = Config::getParam('locale-eu');
2023-06-23 19:01:51 +12:00
$headers['x-appwrite-country-code'] = $record['country']['iso_code'] ?? '';
$headers['x-appwrite-continent-code'] = $record['continent']['code'] ?? '';
$headers['x-appwrite-continent-eu'] = (\in_array($record['country']['iso_code'], $eu)) ? 'true' : 'false';
}
}
$headersFiltered = [];
foreach ($headers as $key => $value) {
2023-08-12 01:34:57 +12:00
if (\in_array(\strtolower($key), FUNCTION_ALLOWLIST_HEADERS_REQUEST)) {
2023-08-10 03:53:58 +12:00
$headersFiltered[] = ['name' => $key, 'value' => $value];
}
}
2023-07-30 04:20:20 +12:00
$executionId = ID::unique();
$execution = new Document([
'$id' => $executionId,
'$permissions' => !$user->isEmpty() ? [Permission::read(Role::user($user->getId()))] : [],
'functionInternalId' => $function->getInternalId(),
'functionId' => $function->getId(),
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentId' => $deployment->getId(),
'trigger' => 'http', // http / schedule / event
'status' => $async ? 'waiting' : 'processing', // waiting / processing / completed / failed
'responseStatusCode' => 0,
'responseHeaders' => [],
'requestPath' => $path,
'requestMethod' => $method,
'requestHeaders' => $headersFiltered,
2023-07-30 04:20:20 +12:00
'errors' => '',
'logs' => '',
'duration' => 0.0,
'search' => implode(' ', [$functionId, $executionId]),
]);
$events
->setParam('functionId', $function->getId())
->setParam('executionId', $execution->getId())
Database layer (#3338) * database response model * database collection config * new database scopes * database service update * database execption codes * remove read write permission from database model * updating tests and fixing some bugs * server side tests are now passing * databases api * tests for database endpoint * composer update * fix error * formatting * formatting fixes * get database test * more updates to events and usage * more usage updates * fix delete type * fix test * delete database * more fixes * databaseId in attributes and indexes * more fixes * fix issues * fix index subquery * fix console scope and index query * updating tests as required * fix phpcs errors and warnings * updates to review suggestions * UI progress * ui updates and cleaning up * fix type * rework database events * update tests * update types * event generation fixed * events config updated * updating context to support multiple * realtime updates * fix ids * update context * validator updates * fix naming conflict * fix tests * fix lint errors * fix wprler and realtime tests * fix webhooks test * fix event validator and other tests * formatting fixes * removing leftover var_dumps * remove leftover comment * update usage params * usage metrics updates * update database usage * fix usage * specs update * updates to usage * fix UI and usage * fix lints * internal id fixes * fixes for internal Id * renaming services and related files * rename tests * rename doc link * rename readme * fix test name * tests: fixes for 0.15.x sync Co-authored-by: Torsten Dittmann <torsten.dittmann@googlemail.com>
2022-06-22 22:51:49 +12:00
->setContext('function', $function);
2021-08-24 21:32:27 +12:00
if ($async) {
2023-07-28 19:56:07 +12:00
if ($function->getAttribute('logging')) {
/** @var Document $execution */
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
}
2022-11-16 23:33:11 +13:00
$queueForFunctions
2022-11-16 07:13:17 +13:00
->setType('http')
->setExecution($execution)
->setFunction($function)
2023-02-15 00:01:38 +13:00
->setBody($body)
->setHeaders($headers)
->setPath($path)
->setMethod($method)
2022-11-16 07:13:17 +13:00
->setJWT($jwt)
->setProject($project)
2022-11-17 01:44:14 +13:00
->setUser($user)
->trigger();
2020-07-13 09:18:52 +12:00
return $response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($execution, Response::MODEL_EXECUTION);
2021-08-24 21:32:27 +12:00
}
2022-01-11 03:18:33 +13:00
2023-03-29 02:21:42 +13:00
$durationStart = \microtime(true);
2023-03-12 05:06:02 +13:00
$vars = [];
// Shared vars
2023-08-18 18:55:44 +12:00
foreach ($project->getAttribute('variables', []) as $var) {
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
2022-08-11 01:43:05 +12:00
2023-03-12 05:06:02 +13:00
// Function vars
$vars = \array_merge($vars, array_reduce($function->getAttribute('vars', []), function (array $carry, Document $var) {
$carry[$var->getAttribute('key')] = $var->getAttribute('value') ?? '';
return $carry;
}, []));
// Appwrite vars
2022-08-11 01:43:05 +12:00
$vars = \array_merge($vars, [
'APPWRITE_FUNCTION_ID' => $functionId,
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'),
'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(),
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
2022-09-19 23:58:41 +12:00
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
2022-02-04 14:29:40 +13:00
]);
2022-02-06 08:49:57 +13:00
/** Execute function */
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
try {
$command = $runtime['startCommand'];
$executionResponse = $executor->createExecution(
projectId: $project->getId(),
deploymentId: $deployment->getId(),
body: \strlen($body) > 0 ? $body : null,
2022-11-08 21:49:45 +13:00
variables: $vars,
timeout: $function->getAttribute('timeout', 0),
2022-11-08 21:49:45 +13:00
image: $runtime['image'],
2023-03-15 19:08:43 +13:00
source: $build->getAttribute('path', ''),
entrypoint: $deployment->getAttribute('entrypoint', ''),
2023-08-19 18:15:47 +12:00
version: $function->getAttribute('version'),
2023-02-15 00:01:38 +13:00
path: $path,
method: $method,
headers: $headers,
2023-06-26 20:11:13 +12:00
runtimeEntrypoint: 'cp /tmp/code.tar.gz /mnt/code/code.tar.gz && nohup helpers/start.sh "' . $command . '"'
);
$headersFiltered = [];
foreach ($executionResponse['headers'] as $key => $value) {
2023-08-12 01:34:57 +12:00
if (\in_array(\strtolower($key), FUNCTION_ALLOWLIST_HEADERS_RESPONSE)) {
2023-08-10 03:53:58 +12:00
$headersFiltered[] = ['name' => $key, 'value' => $value];
}
}
2023-07-30 04:20:20 +12:00
/** Update execution status */
2023-03-07 00:16:34 +13:00
$status = $executionResponse['statusCode'] >= 400 ? 'failed' : 'completed';
2023-02-03 08:21:00 +13:00
$execution->setAttribute('status', $status);
2023-07-30 04:20:20 +12:00
$execution->setAttribute('responseStatusCode', $executionResponse['statusCode']);
$execution->setAttribute('responseHeaders', $headersFiltered);
2023-02-15 00:01:38 +13:00
$execution->setAttribute('logs', $executionResponse['logs']);
$execution->setAttribute('errors', $executionResponse['errors']);
$execution->setAttribute('duration', $executionResponse['duration']);
2022-12-15 22:45:43 +13:00
/**
* Sync execution compute usage from
*/
$queueForUsage
2023-08-10 03:53:58 +12:00
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($executionResponse['duration'] * 1000)) // per project
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($executionResponse['duration'] * 1000)) // per function
2022-12-15 22:45:43 +13:00
;
} catch (\Throwable $th) {
2023-03-29 02:21:42 +13:00
$durationEnd = \microtime(true);
2022-07-03 02:25:44 +12:00
$execution
2023-03-29 02:21:42 +13:00
->setAttribute('duration', $durationEnd - $durationStart)
2022-07-03 02:25:44 +12:00
->setAttribute('status', 'failed')
2023-07-30 04:20:20 +12:00
->setAttribute('responseStatusCode', 500)
2023-02-15 21:36:20 +13:00
->setAttribute('errors', $th->getMessage() . '\nError Code: ' . $th->getCode());
Console::error($th->getMessage());
}
2022-02-04 14:29:40 +13:00
2023-03-15 08:31:23 +13:00
if ($function->getAttribute('logging')) {
2023-07-28 19:56:07 +12:00
/** @var Document $execution */
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
2023-02-24 22:24:20 +13:00
}
2022-02-25 01:58:10 +13:00
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
2022-08-16 17:23:51 +12:00
if (!$isPrivilegedUser && !$isAppUser) {
2023-02-15 00:01:38 +13:00
$execution->setAttribute('logs', '');
$execution->setAttribute('errors', '');
2022-08-16 16:34:49 +12:00
}
2022-07-13 20:57:57 +12:00
2023-08-07 01:11:30 +12:00
$headers = [];
2023-08-16 18:19:42 +12:00
foreach (($executionResponse['headers'] ?? []) as $key => $value) {
2023-08-10 03:53:58 +12:00
$headers[] = ['name' => $key, 'value' => $value];
2023-08-07 01:11:30 +12:00
}
2023-08-16 18:19:42 +12:00
$execution->setAttribute('responseBody', $executionResponse['body'] ?? '');
2023-08-07 01:11:30 +12:00
$execution->setAttribute('responseHeaders', $headers);
2023-02-27 23:20:37 +13:00
2022-01-11 03:18:33 +13:00
$response
2022-01-27 12:19:02 +13:00
->setStatusCode(Response::STATUS_CODE_CREATED)
2022-02-25 02:22:20 +13:00
->dynamic($execution, Response::MODEL_EXECUTION);
2020-12-27 04:20:08 +13:00
});
2020-07-13 09:18:52 +12:00
App::get('/v1/functions/:functionId/executions')
->groups(['api', 'functions'])
2020-05-06 05:30:12 +12:00
->desc('List Executions')
2020-12-30 20:26:01 +13:00
->label('scope', 'execution.read')
2021-04-16 19:22:17 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
2020-05-06 05:30:12 +12:00
->label('sdk.namespace', 'functions')
->label('sdk.method', 'listExecutions')
->label('sdk.description', '/docs/references/functions/list-executions.md')
2020-11-12 10:02:24 +13:00
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_EXECUTION_LIST)
->param('functionId', '', new UID(), 'Function ID.')
2023-03-30 08:38:39 +13:00
->param('queries', [], new Executions(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Executions::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
2020-12-27 04:20:08 +13:00
->inject('response')
->inject('dbForProject')
->inject('mode')
->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject, string $mode) {
2020-05-06 05:30:12 +12:00
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
2020-05-06 05:30:12 +12:00
if ($function->isEmpty() || !$function->getAttribute('enabled')) {
if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
2020-07-13 09:18:52 +12:00
}
2021-05-27 22:09:14 +12:00
2022-08-23 21:26:34 +12:00
$queries = Query::parseQueries($queries);
2021-08-07 00:36:05 +12:00
2022-08-12 11:53:52 +12:00
if (!empty($search)) {
2022-08-23 21:26:34 +12:00
$queries[] = Query::search('search', $search);
2021-08-07 00:36:05 +12:00
}
2022-08-23 21:26:34 +12:00
// Set internal queries
$queries[] = Query::equal('functionId', [$function->getId()]);
2022-08-23 21:26:34 +12:00
// Get cursor document if there was a cursor query
2023-05-01 21:18:50 +12:00
$cursor = Query::getByType($queries, [Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE]);
$cursor = reset($cursor);
2022-08-30 23:55:23 +12:00
if ($cursor) {
2022-08-23 21:26:34 +12:00
/** @var Query $cursor */
$executionId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('executions', $executionId);
2021-08-07 00:36:05 +12:00
2022-08-12 11:53:52 +12:00
if ($cursorDocument->isEmpty()) {
2022-08-23 21:26:34 +12:00
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Execution '{$executionId}' for the 'cursor' value not found.");
2021-08-07 00:36:05 +12:00
}
2022-08-23 21:26:34 +12:00
$cursor->setValue($cursorDocument);
}
2022-08-23 21:26:34 +12:00
$filterQueries = Query::groupByType($queries)['filters'];
$results = $dbForProject->find('executions', $queries);
2022-08-12 11:53:52 +12:00
$total = $dbForProject->count('executions', $filterQueries, APP_LIMIT_COUNT);
2020-07-13 09:18:52 +12:00
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
2022-08-16 16:34:49 +12:00
if (!$isPrivilegedUser && !$isAppUser) {
$results = array_map(function ($execution) {
2023-02-15 00:01:38 +13:00
$execution->setAttribute('logs', '');
$execution->setAttribute('errors', '');
2022-08-14 21:23:41 +12:00
return $execution;
}, $results);
}
2020-07-13 09:18:52 +12:00
2020-10-31 08:53:27 +13:00
$response->dynamic(new Document([
2021-05-27 22:09:14 +12:00
'executions' => $results,
2022-02-27 22:57:09 +13:00
'total' => $total,
2020-10-31 08:53:27 +13:00
]), Response::MODEL_EXECUTION_LIST);
2020-12-27 04:20:08 +13:00
});
2020-07-13 09:18:52 +12:00
App::get('/v1/functions/:functionId/executions/:executionId')
->groups(['api', 'functions'])
2020-05-06 05:30:12 +12:00
->desc('Get Execution')
2020-12-30 20:26:01 +13:00
->label('scope', 'execution.read')
2021-04-16 19:22:17 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
2020-05-06 05:30:12 +12:00
->label('sdk.namespace', 'functions')
->label('sdk.method', 'getExecution')
->label('sdk.description', '/docs/references/functions/get-execution.md')
2020-11-12 10:02:24 +13:00
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_EXECUTION)
->param('functionId', '', new UID(), 'Function ID.')
->param('executionId', '', new UID(), 'Execution ID.')
2020-12-27 04:20:08 +13:00
->inject('response')
->inject('dbForProject')
->inject('mode')
->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, string $mode) {
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
2020-05-06 05:30:12 +12:00
if ($function->isEmpty() || !$function->getAttribute('enabled')) {
if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
2020-07-13 09:18:52 +12:00
}
2020-05-06 05:30:12 +12:00
$execution = $dbForProject->getDocument('executions', $executionId);
2020-05-06 05:30:12 +12:00
2020-10-28 08:46:15 +13:00
if ($execution->getAttribute('functionId') !== $function->getId()) {
throw new Exception(Exception::EXECUTION_NOT_FOUND);
2020-07-13 09:18:52 +12:00
}
2020-05-06 05:30:12 +12:00
2021-06-21 01:59:36 +12:00
if ($execution->isEmpty()) {
throw new Exception(Exception::EXECUTION_NOT_FOUND);
2020-05-06 05:30:12 +12:00
}
2020-07-13 09:18:52 +12:00
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
2022-08-16 17:23:51 +12:00
if (!$isPrivilegedUser && !$isAppUser) {
2023-02-15 00:01:38 +13:00
$execution->setAttribute('logs', '');
$execution->setAttribute('errors', '');
2020-05-06 05:30:12 +12:00
}
2020-07-13 09:18:52 +12:00
2020-10-31 08:53:27 +13:00
$response->dynamic($execution, Response::MODEL_EXECUTION);
2020-12-27 04:20:08 +13:00
});
2022-08-02 03:13:47 +12:00
// Variables
2022-08-10 03:29:24 +12:00
App::post('/v1/functions/:functionId/variables')
2022-08-02 03:13:47 +12:00
->desc('Create Variable')
->groups(['api', 'functions'])
->label('scope', 'functions.write')
2022-09-05 20:00:08 +12:00
->label('audits.event', 'variable.create')
2022-09-04 20:13:44 +12:00
->label('audits.resource', 'function/{request.functionId}')
2022-08-04 01:32:50 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2022-08-02 03:13:47 +12:00
->label('sdk.namespace', 'functions')
->label('sdk.method', 'createVariable')
->label('sdk.description', '/docs/references/functions/create-variable.md')
2022-08-02 03:13:47 +12:00
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VARIABLE)
2022-09-19 22:05:42 +12:00
->param('functionId', '', new UID(), 'Function unique ID.', false)
->param('key', null, new Text(Database::LENGTH_KEY), 'Variable key. Max length: ' . Database::LENGTH_KEY . ' chars.', false)
2023-08-13 07:08:44 +12:00
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false)
2023-08-08 21:28:25 +12:00
->inject('project')
2022-08-02 03:13:47 +12:00
->inject('response')
->inject('dbForProject')
2023-03-19 22:43:57 +13:00
->inject('dbForConsole')
->action(function (string $functionId, string $key, string $value, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) {
2022-08-02 03:13:47 +12:00
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2022-08-02 03:13:47 +12:00
}
2022-08-27 01:38:39 +12:00
$variableId = ID::unique();
2022-08-02 03:13:47 +12:00
$variable = new Document([
2022-08-27 01:38:39 +12:00
'$id' => $variableId,
2022-08-25 03:07:18 +12:00
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
2023-03-12 05:06:02 +13:00
'resourceInternalId' => $function->getInternalId(),
'resourceId' => $function->getId(),
'resourceType' => 'function',
2022-08-02 03:13:47 +12:00
'key' => $key,
2022-08-27 01:38:39 +12:00
'value' => $value,
2023-03-12 05:06:02 +13:00
'search' => implode(' ', [$variableId, $function->getId(), $key, 'function']),
2022-08-02 03:13:47 +12:00
]);
try {
$variable = $dbForProject->createDocument('variables', $variable);
} catch (DuplicateException $th) {
throw new Exception(Exception::VARIABLE_ALREADY_EXISTS);
2022-08-02 03:13:47 +12:00
}
2023-07-28 19:56:07 +12:00
$dbForConsole->deleteCachedDocument('projects', $project->getId());
2022-08-02 03:13:47 +12:00
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false));
2023-07-28 19:56:07 +12:00
// Inform scheduler to pull the latest changes
2023-03-19 22:43:57 +13:00
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
$schedule
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $function->getAttribute('schedule'))
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
2022-08-02 03:13:47 +12:00
$dbForProject->deleteCachedDocument('functions', $function->getId());
2022-09-07 23:11:10 +12:00
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($variable, Response::MODEL_VARIABLE);
2022-08-02 03:13:47 +12:00
});
2022-08-10 03:29:24 +12:00
App::get('/v1/functions/:functionId/variables')
2022-08-02 03:13:47 +12:00
->desc('List Variables')
->groups(['api', 'functions'])
->label('scope', 'functions.read')
2022-08-04 01:32:50 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2022-08-02 03:13:47 +12:00
->label('sdk.namespace', 'functions')
->label('sdk.method', 'listVariables')
->label('sdk.description', '/docs/references/functions/list-variables.md')
2022-08-02 03:13:47 +12:00
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VARIABLE_LIST)
2022-09-19 22:05:42 +12:00
->param('functionId', '', new UID(), 'Function unique ID.', false)
2022-08-02 03:13:47 +12:00
->inject('response')
->inject('dbForProject')
->action(function (string $functionId, Response $response, Database $dbForProject) {
2022-08-02 03:13:47 +12:00
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2022-08-02 03:13:47 +12:00
}
$response->dynamic(new Document([
'variables' => $function->getAttribute('vars'),
'total' => \count($function->getAttribute('vars')),
2022-08-02 03:13:47 +12:00
]), Response::MODEL_VARIABLE_LIST);
});
2022-08-10 03:29:24 +12:00
App::get('/v1/functions/:functionId/variables/:variableId')
2022-08-02 03:13:47 +12:00
->desc('Get Variable')
->groups(['api', 'functions'])
->label('scope', 'functions.read')
2022-08-04 01:32:50 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2022-08-02 03:13:47 +12:00
->label('sdk.namespace', 'functions')
->label('sdk.method', 'getVariable')
->label('sdk.description', '/docs/references/functions/get-variable.md')
2022-08-02 03:13:47 +12:00
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VARIABLE)
2022-09-19 22:05:42 +12:00
->param('functionId', '', new UID(), 'Function unique ID.', false)
->param('variableId', '', new UID(), 'Variable unique ID.', false)
2022-08-02 03:13:47 +12:00
->inject('response')
->inject('dbForProject')
2022-08-02 22:05:58 +12:00
->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject) {
2022-08-02 03:13:47 +12:00
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2022-08-02 03:13:47 +12:00
}
2023-07-25 01:12:36 +12:00
$variable = $dbForProject->getDocument('variables', $variableId);
2023-08-18 18:55:44 +12:00
if (
$variable === false ||
$variable->isEmpty() ||
$variable->getAttribute('resourceInternalId') !== $function->getInternalId() ||
$variable->getAttribute('resourceType') !== 'function'
) {
2023-07-25 01:12:36 +12:00
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
2022-08-02 03:13:47 +12:00
if ($variable === false || $variable->isEmpty()) {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
2022-08-02 03:13:47 +12:00
}
$response->dynamic($variable, Response::MODEL_VARIABLE);
});
2022-08-10 03:29:24 +12:00
App::put('/v1/functions/:functionId/variables/:variableId')
2022-08-02 03:13:47 +12:00
->desc('Update Variable')
->groups(['api', 'functions'])
->label('scope', 'functions.write')
2022-09-05 20:00:08 +12:00
->label('audits.event', 'variable.update')
2022-09-04 20:13:44 +12:00
->label('audits.resource', 'function/{request.functionId}')
2022-08-04 01:32:50 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2022-08-02 03:13:47 +12:00
->label('sdk.namespace', 'functions')
->label('sdk.method', 'updateVariable')
->label('sdk.description', '/docs/references/functions/update-variable.md')
2022-08-02 03:13:47 +12:00
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_VARIABLE)
2022-09-19 22:05:42 +12:00
->param('functionId', '', new UID(), 'Function unique ID.', false)
->param('variableId', '', new UID(), 'Variable unique ID.', false)
->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false)
2023-08-13 07:08:44 +12:00
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true)
2023-03-15 00:13:03 +13:00
->inject('project')
2022-08-02 03:13:47 +12:00
->inject('response')
->inject('dbForProject')
2023-03-19 22:43:57 +13:00
->inject('dbForConsole')
->action(function (string $functionId, string $variableId, string $key, ?string $value, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) {
2022-08-02 03:13:47 +12:00
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2022-08-02 03:13:47 +12:00
}
2023-07-25 01:12:36 +12:00
$variable = $dbForProject->getDocument('variables', $variableId);
if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceInternalId') !== $function->getInternalId() || $variable->getAttribute('resourceType') !== 'function') {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
2022-08-02 03:13:47 +12:00
if ($variable === false || $variable->isEmpty()) {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
2022-08-02 03:13:47 +12:00
}
$variable
->setAttribute('key', $key)
2022-08-02 03:13:47 +12:00
->setAttribute('value', $value ?? $variable->getAttribute('value'))
2023-03-29 02:21:42 +13:00
->setAttribute('search', implode(' ', [$variableId, $function->getId(), $key, 'function']));
2022-08-02 03:13:47 +12:00
try {
$dbForProject->updateDocument('variables', $variable->getId(), $variable);
} catch (DuplicateException $th) {
throw new Exception(Exception::VARIABLE_ALREADY_EXISTS);
2022-08-02 03:13:47 +12:00
}
2023-07-28 19:56:07 +12:00
$dbForConsole->deleteCachedDocument('projects', $project->getId());
2022-08-02 03:13:47 +12:00
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false));
2023-07-28 19:56:07 +12:00
// Inform scheduler to pull the latest changes
2023-03-19 22:43:57 +13:00
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
$schedule
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $function->getAttribute('schedule'))
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
2022-08-02 03:13:47 +12:00
$dbForProject->deleteCachedDocument('functions', $function->getId());
$response->dynamic($variable, Response::MODEL_VARIABLE);
});
2022-08-10 03:29:24 +12:00
App::delete('/v1/functions/:functionId/variables/:variableId')
2022-08-02 03:13:47 +12:00
->desc('Delete Variable')
->groups(['api', 'functions'])
->label('scope', 'functions.write')
2022-09-05 20:00:08 +12:00
->label('audits.event', 'variable.delete')
2022-09-04 20:13:44 +12:00
->label('audits.resource', 'function/{request.functionId}')
2022-08-04 01:32:50 +12:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2022-08-02 03:13:47 +12:00
->label('sdk.namespace', 'functions')
->label('sdk.method', 'deleteVariable')
->label('sdk.description', '/docs/references/functions/delete-variable.md')
2022-08-02 03:13:47 +12:00
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
2022-09-19 22:05:42 +12:00
->param('functionId', '', new UID(), 'Function unique ID.', false)
->param('variableId', '', new UID(), 'Variable unique ID.', false)
2023-03-15 00:13:03 +13:00
->inject('project')
2022-08-02 03:13:47 +12:00
->inject('response')
->inject('dbForProject')
2023-03-19 22:43:57 +13:00
->inject('dbForConsole')
->action(function (string $functionId, string $variableId, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) {
2022-08-02 03:13:47 +12:00
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2022-08-02 03:13:47 +12:00
}
2023-07-25 01:12:36 +12:00
$variable = $dbForProject->getDocument('variables', $variableId);
if ($variable === false || $variable->isEmpty() || $variable->getAttribute('resourceInternalId') !== $function->getInternalId() || $variable->getAttribute('resourceType') !== 'function') {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
2022-08-02 03:13:47 +12:00
if ($variable === false || $variable->isEmpty()) {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
2022-08-02 03:13:47 +12:00
}
$dbForProject->deleteDocument('variables', $variable->getId());
2023-03-19 22:43:57 +13:00
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false));
2023-07-28 19:56:07 +12:00
// Inform scheduler to pull the latest changes
2023-03-19 22:43:57 +13:00
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
$schedule
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $function->getAttribute('schedule'))
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
2022-08-02 03:13:47 +12:00
$dbForProject->deleteCachedDocument('functions', $function->getId());
$response->noContent();
});