1
0
Fork 0
mirror of synced 2024-07-05 06:31:08 +12:00
appwrite/app/controllers/api/functions.php

2156 lines
99 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;
2023-10-25 22:46:45 +13:00
use Appwrite\Event\Usage;
use Appwrite\Event\Validator\FunctionEvent;
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;
2024-02-13 05:02:04 +13:00
use Utopia\Database\Exception\Query as QueryException;
2023-02-15 00:01:38 +13:00
use Utopia\Validator\Assoc;
2023-06-11 22:29:04 +12:00
use Utopia\Database\Helpers\ID;
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;
2023-10-28 03:08:33 +13:00
use Utopia\VCS\Exception\RepositoryNotFound;
2020-05-05 02:35:01 +12:00
include_once __DIR__ . '/../shared/api.php';
$redeployVcs = function (Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Build $queueForBuilds, 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', '');
2023-10-28 03:08:33 +13:00
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
2023-08-10 03:53:58 +12:00
$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(),
2023-10-03 03:02:48 +13:00
'resourceInternalId' => $function->getInternalId(),
2023-06-17 02:43:37 +12:00
'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
$queueForBuilds
2023-06-17 02:43:37 +12:00
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
2024-02-21 00:40:55 +13:00
->setTemplate($template);
2023-06-17 02:43:37 +12:00
};
2020-07-13 09:18:52 +12:00
App::post('/v1/functions')
->groups(['api', 'functions'])
2023-10-03 03:02:48 +13:00
->desc('Create function')
2020-05-05 02:35:01 +12:00
->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-06-11 22:29:04 +12: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 role strings with execution permissions. By default no user is granted with any execute permissions. [learn more about roles](https://appwrite.io/docs/permissions#permission-roles). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true)
->param('events', [], new ArrayList(new FunctionEvent(), 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? When set to \'disabled\', users cannot access the function but Server SDKs with and API key can still access the function. No data is lost when this is toggled.', true)
->param('logging', true, new Boolean(), 'Whether executions will be logged. When set to false, executions will not be logged, but will reduce resource used by your Appwrite project.', true)
->param('entrypoint', '', new Text(1028, 0), 'Entrypoint File. This path is relative to the "providerRootDirectory".', true)
2023-08-18 19:14:13 +12:00
->param('commands', '', new Text(8192, 0), 'Build Commands.', true)
2023-08-23 20:29:01 +12:00
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Control System) 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)
2023-08-23 20:29:01 +12:00
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true)
->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true)
->param('templateRepository', '', new Text(128, 0), 'Repository name of the template.', true)
->param('templateOwner', '', new Text(128, 0), 'The name of the owner 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), 'Production branch for the repo linked to the function template.', 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')
2022-12-21 05:11:30 +13:00
->inject('queueForEvents')
->inject('queueForBuilds')
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 $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
2022-08-15 02:22:38 +12:00
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
2024-02-20 23:27:40 +13:00
$allowList = \array_filter(\explode(',', App::getEnv('_APP_FUNCTIONS_RUNTIMES', '')));
if (!empty($allowList) && !\in_array($runtime, $allowList)) {
throw new Exception(Exception::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $runtime . '" is not supported');
}
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".');
}
$function = $dbForProject->createDocument('functions', new Document([
'$id' => $functionId,
2021-05-05 09:25:17 +12:00
'execute' => $execute,
'enabled' => $enabled,
2023-07-28 19:56:07 +12:00
'live' => true,
'logging' => $logging,
2020-07-13 09:18:52 +12:00
'name' => $name,
'runtime' => $runtime,
2023-06-11 22:29:04 +12:00
'deploymentInternalId' => '',
'deployment' => '',
2020-07-13 09:18:52 +12:00
'events' => $events,
'schedule' => $schedule,
2023-06-11 22:29:04 +12:00
'scheduleInternalId' => '',
2023-08-22 05:43:03 +12:00
'scheduleId' => '',
2020-07-13 09:18:52 +12:00
'timeout' => $timeout,
2023-07-28 19:56:07 +12:00
'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,
2021-05-05 09:25:17 +12:00
]));
2020-05-06 05:30:12 +12:00
2022-11-16 05:14:52 +13:00
$schedule = Authorization::skip(
2023-08-22 05:43:03 +12:00
fn () => $dbForConsole->createDocument('schedules', new Document([
2022-11-17 01:51:43 +13:00
'region' => App::getEnv('_APP_REGION', 'default'), // Todo replace with projects region
2022-11-07 10:41:33 +13:00
'resourceType' => 'function',
'resourceId' => $function->getId(),
2023-06-11 22:29:04 +12:00
'resourceInternalId' => $function->getInternalId(),
2022-11-07 10:41:33 +13:00
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
2022-11-08 04:42:08 +13:00
'schedule' => $function->getAttribute('schedule'),
2022-11-07 10:41:33 +13:00
'active' => false,
]))
);
2022-11-16 05:14:52 +13:00
$function->setAttribute('scheduleId', $schedule->getId());
2023-06-11 22:29:04 +12:00
$function->setAttribute('scheduleInternalId', $schedule->getInternalId());
2023-08-22 05:43:03 +12:00
// Git connect logic
if (!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' => []
]));
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)) {
$redeployVcs($request, $function, $project, $installation, $dbForProject, $queueForBuilds, $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();
2023-10-02 06:39:26 +13:00
$ruleCreate =
$queueForEvents
2023-10-28 03:08:33 +13:00
->setClass(Event::WEBHOOK_CLASS_NAME)
->setQueue(Event::WEBHOOK_QUEUE_NAME);
2023-10-02 06:39:26 +13:00
2023-03-11 01:20:24 +13:00
$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
}
2022-09-23 18:12:17 +12:00
2022-12-21 05:11:30 +13:00
$queueForEvents->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'])
2023-10-03 03:02:48 +13:00
->desc('List functions')
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')
->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')
2022-08-23 21:02:06 +12:00
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
2020-07-13 09:18:52 +12:00
2024-02-13 05:02:04 +13:00
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
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
}
2024-02-12 22:55:45 +13:00
/**
2024-02-12 23:03:31 +13:00
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
2024-02-12 22:55:45 +13:00
*/
2023-08-22 15:25:55 +12:00
$cursor = \array_filter($queries, function ($query) {
2023-12-07 03:10:40 +13:00
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
2023-08-22 15:25:55 +12:00
});
$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([
2022-08-23 21:02:06 +12: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');
$allowList = \array_filter(\explode(',', App::getEnv('_APP_FUNCTIONS_RUNTIMES', '')));
$allowed = [];
foreach ($runtimes as $key => $runtime) {
if (!empty($allowList) && !\in_array($key, $allowList)) {
continue;
}
2021-12-10 02:02:12 +13:00
$runtimes[$key]['$id'] = $key;
$allowed[] = $runtimes[$key];
}
2021-12-10 02:02:12 +13:00
$response->dynamic(new Document([
'total' => count($allowed),
'runtimes' => $allowed
2021-12-10 02:02:12 +13:00
]), Response::MODEL_RUNTIME_LIST);
});
2020-07-13 09:18:52 +12:00
App::get('/v1/functions/:functionId')
->groups(['api', 'functions'])
2023-10-03 03:02:48 +13:00
->desc('Get function')
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')
->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')
2022-05-25 02:28:27 +12: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')
2023-10-03 03:02:48 +13:00
->desc('Get function usage')
2023-09-28 06:10:21 +13:00
->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)
2024-01-09 16:10:43 +13:00
->label('sdk.response.model', Response::MODEL_USAGE_FUNCTION)
->param('functionId', '', new UID(), 'Function ID.')
2023-11-08 22:09:32 +13:00
->param('range', '30d', new WhiteList(['24h', '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-10-25 20:39:59 +13:00
$periods = Config::getParam('usage', []);
$stats = $usage = [];
$days = $periods[$range];
$metrics = [
str_replace(['{resourceType}', '{resourceInternalId}'], ['functions', $function->getInternalId()], METRIC_FUNCTION_ID_DEPLOYMENTS),
str_replace(['{resourceType}', '{resourceInternalId}'], ['functions', $function->getInternalId()], METRIC_FUNCTION_ID_DEPLOYMENTS_STORAGE),
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-11-08 22:09:32 +13:00
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $metric) {
2024-02-01 23:21:50 +13:00
$result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
2023-10-16 06:41:09 +13:00
2023-11-08 22:09:32 +13:00
$stats[$metric]['total'] = $result['value'] ?? 0;
2023-10-25 20:39:59 +13:00
$limit = $days['limit'];
$period = $days['period'];
2024-02-01 23:21:50 +13:00
$results = $dbForProject->find('stats', [
2023-10-25 20:39:59 +13:00
Query::equal('metric', [$metric]),
2023-10-26 01:06:54 +13:00
Query::equal('period', [$period]),
2023-10-25 20:39:59 +13:00
Query::limit($limit),
Query::orderDesc('time'),
]);
2023-11-08 22:09:32 +13:00
$stats[$metric]['data'] = [];
2023-10-25 20:39:59 +13:00
foreach ($results as $result) {
2023-11-08 22:09:32 +13:00
$stats[$metric]['data'][$result->getAttribute('time')] = [
'value' => $result->getAttribute('value'),
2023-10-25 20:39:59 +13:00
];
2023-10-16 06:41:09 +13:00
}
2023-10-25 20:39:59 +13:00
}
});
2023-06-11 22:29:04 +12:00
2023-10-25 20:39:59 +13:00
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
};
2023-12-14 17:51:36 +13:00
foreach ($metrics as $metric) {
$usage[$metric]['total'] = $stats[$metric]['total'];
$usage[$metric]['data'] = [];
$leap = time() - ($days['limit'] * $days['factor']);
while ($leap < time()) {
$leap += $days['factor'];
$formatDate = date($format, $leap);
$usage[$metric]['data'][] = [
'value' => $stats[$metric]['data'][$formatDate]['value'] ?? 0,
'date' => $formatDate,
];
2023-06-11 22:29:04 +12:00
}
2023-12-14 17:51:36 +13:00
}
2023-10-25 20:39:59 +13:00
$response->dynamic(new Document([
'range' => $range,
2023-11-08 22:09:32 +13:00
'deploymentsTotal' => $usage[$metrics[0]]['total'],
'deploymentsStorageTotal' => $usage[$metrics[1]]['total'],
'buildsTotal' => $usage[$metrics[2]]['total'],
'buildsStorageTotal' => $usage[$metrics[3]]['total'],
'buildsTimeTotal' => $usage[$metrics[4]]['total'],
'executionsTotal' => $usage[$metrics[5]]['total'],
'executionsTimeTotal' => $usage[$metrics[6]]['total'],
'deployments' => $usage[$metrics[0]]['data'],
'deploymentsStorage' => $usage[$metrics[1]]['data'],
'builds' => $usage[$metrics[2]]['data'],
'buildsStorage' => $usage[$metrics[3]]['data'],
'buildsTime' => $usage[$metrics[4]]['data'],
'executions' => $usage[$metrics[5]]['data'],
'executionsTime' => $usage[$metrics[6]]['data'],
2023-10-25 20:39:59 +13:00
]), Response::MODEL_USAGE_FUNCTION);
2022-07-17 22:30:58 +12:00
});
App::get('/v1/functions/usage')
2023-10-04 05:50:48 +13:00
->desc('Get functions usage')
2023-10-25 20:39:59 +13:00
->groups(['api', 'functions'])
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)
2023-11-08 22:09:32 +13:00
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
2022-07-17 22:30:58 +12:00
->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-10-25 20:39:59 +13:00
$periods = Config::getParam('usage', []);
$stats = $usage = [];
$days = $periods[$range];
$metrics = [
METRIC_FUNCTIONS,
METRIC_DEPLOYMENTS,
METRIC_DEPLOYMENTS_STORAGE,
METRIC_BUILDS,
METRIC_BUILDS_STORAGE,
METRIC_BUILDS_COMPUTE,
METRIC_EXECUTIONS,
METRIC_EXECUTIONS_COMPUTE,
];
2023-11-08 22:09:32 +13:00
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $metric) {
2024-02-01 23:21:50 +13:00
$result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
2023-10-16 06:41:09 +13:00
2023-11-08 22:09:32 +13:00
$stats[$metric]['total'] = $result['value'] ?? 0;
2023-10-25 20:39:59 +13:00
$limit = $days['limit'];
$period = $days['period'];
2024-02-01 23:21:50 +13:00
$results = $dbForProject->find('stats', [
2023-10-25 20:39:59 +13:00
Query::equal('metric', [$metric]),
2023-10-26 01:06:54 +13:00
Query::equal('period', [$period]),
2023-10-25 20:39:59 +13:00
Query::limit($limit),
Query::orderDesc('time'),
]);
2023-11-08 22:09:32 +13:00
$stats[$metric]['data'] = [];
2023-10-25 20:39:59 +13:00
foreach ($results as $result) {
2023-11-08 22:09:32 +13:00
$stats[$metric]['data'][$result->getAttribute('time')] = [
'value' => $result->getAttribute('value'),
2023-10-25 20:39:59 +13:00
];
2023-10-16 06:41:09 +13:00
}
2023-10-25 20:39:59 +13:00
}
});
2023-10-16 06:41:09 +13:00
2023-10-25 20:39:59 +13:00
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
};
2023-12-14 17:51:36 +13:00
foreach ($metrics as $metric) {
$usage[$metric]['total'] = $stats[$metric]['total'];
$usage[$metric]['data'] = [];
$leap = time() - ($days['limit'] * $days['factor']);
while ($leap < time()) {
$leap += $days['factor'];
$formatDate = date($format, $leap);
$usage[$metric]['data'][] = [
'value' => $stats[$metric]['data'][$formatDate]['value'] ?? 0,
'date' => $formatDate,
];
2023-06-11 22:29:04 +12:00
}
2023-12-14 17:51:36 +13:00
}
2023-10-25 20:39:59 +13:00
$response->dynamic(new Document([
'range' => $range,
2023-11-08 22:09:32 +13:00
'functionsTotal' => $usage[$metrics[0]]['total'],
'deploymentsTotal' => $usage[$metrics[1]]['total'],
'deploymentsStorageTotal' => $usage[$metrics[2]]['total'],
'buildsTotal' => $usage[$metrics[3]]['total'],
'buildsStorageTotal' => $usage[$metrics[4]]['total'],
'buildsTimeTotal' => $usage[$metrics[5]]['total'],
'executionsTotal' => $usage[$metrics[6]]['total'],
'executionsTimeTotal' => $usage[$metrics[7]]['total'],
'functions' => $usage[$metrics[0]]['data'],
'deployments' => $usage[$metrics[1]]['data'],
'deploymentsStorage' => $usage[$metrics[2]]['data'],
'builds' => $usage[$metrics[3]]['data'],
'buildsStorage' => $usage[$metrics[4]]['data'],
'buildsTime' => $usage[$metrics[5]]['data'],
'executions' => $usage[$metrics[6]]['data'],
'executionsTime' => $usage[$metrics[7]]['data'],
2023-10-25 20:39:59 +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'])
2023-10-03 03:02:48 +13:00
->desc('Update function')
2020-05-06 05:30:12 +12:00
->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.')
->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.', true)
->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of role strings with execution permissions. By default no user is granted with any execute permissions. [learn more about roles](https://appwrite.io/docs/permissions#permission-roles). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true)
->param('events', [], new ArrayList(new FunctionEvent(), 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? When set to \'disabled\', users cannot access the function but Server SDKs with and API key can still access the function. No data is lost when this is toggled.', true)
->param('logging', true, new Boolean(), 'Whether executions will be logged. When set to false, executions will not be logged, but will reduce resource used by your Appwrite project.', true)
->param('entrypoint', '', new Text(1028, 0), 'Entrypoint File. This path is relative to the "providerRootDirectory".', true)
2023-08-18 19:14:13 +12:00
->param('commands', '', new Text(8192, 0), 'Build Commands.', true)
2023-08-23 20:29:01 +12:00
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Controle System) deployment.', true)
2023-08-08 03:37:36 +12:00
->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)
2023-08-23 20:29:01 +12:00
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true)
->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')
2022-12-21 05:11:30 +13:00
->inject('queueForEvents')
->inject('queueForBuilds')
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 $queueForEvents, Build $queueForBuilds, 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);
}
if (empty($runtime)) {
$runtime = $function->getAttribute('runtime');
}
$enabled ??= $function->getAttribute('enabled', true);
$repositoryId = $function->getAttribute('repositoryId', '');
$repositoryInternalId = $function->getAttribute('repositoryInternalId', '');
2023-08-23 01:16:07 +12:00
if (empty($entrypoint)) {
$entrypoint = $function->getAttribute('entrypoint', '');
}
$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)) {
$redeployVcs($request, $function, $project, $installation, $dbForProject, $queueForBuilds, 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-06-11 22:29:04 +12: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
2022-12-21 05:11:30 +13:00
$queueForEvents->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')
2024-02-21 03:10:51 +13:00
->inject('deviceForFunctions')
->action(function (string $functionId, string $deploymentId, Response $response, Request $request, Database $dbForProject, Device $deviceForFunctions) {
2023-08-21 22:21:18 +12:00
$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', '');
2024-02-21 03:10:51 +13:00
if (!$deviceForFunctions->exists($path)) {
2023-08-21 22:21:18 +12:00
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())
2023-10-28 03:08:33 +13:00
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"');
2023-08-21 22:21:18 +12:00
2024-02-21 03:10:51 +13:00
$size = $deviceForFunctions->getFileSize($path);
2023-08-21 22:21:18 +12:00
$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);
2024-02-21 03:10:51 +13:00
$response->send($deviceForFunctions->read($path, $start, ($end - $start + 1)));
2023-08-21 22:21:18 +12:00
}
if ($size > APP_STORAGE_READ_BUFFER) {
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
$response->chunk(
2024-02-21 03:10:51 +13:00
$deviceForFunctions->read(
2023-08-21 22:21:18 +12:00
$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 {
2024-02-21 03:10:51 +13:00
$response->send($deviceForFunctions->read($path));
2023-08-21 22:21:18 +12:00
}
});
App::patch('/v1/functions/:functionId/deployments/:deploymentId')
2020-07-13 09:18:52 +12:00
->groups(['api', 'functions'])
2023-10-03 03:02:48 +13:00
->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')
2022-12-21 05:11:30 +13:00
->inject('queueForEvents')
2022-11-07 10:41:33 +13:00
->inject('dbForConsole')
2023-10-03 03:02:48 +13:00
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, 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(), [
2023-06-11 22:29:04 +12:00
'deploymentInternalId' => $deployment->getInternalId(),
'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-06-11 22:29:04 +12: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
2022-12-21 05:11:30 +13:00
$queueForEvents
->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'])
2023-10-03 03:02:48 +13:00
->desc('Delete function')
2020-05-06 05:30:12 +12:00
->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')
2022-12-21 05:11:30 +13:00
->inject('queueForDeletes')
->inject('queueForEvents')
2022-11-04 18:12:08 +13:00
->inject('dbForConsole')
2023-10-03 03:02:48 +13:00
->action(function (string $functionId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, 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-06-11 22:29:04 +12: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
2022-12-21 05:11:30 +13:00
$queueForDeletes
2022-04-18 08:34:32 +12:00
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($function);
2022-12-21 05:11:30 +13:00
$queueForEvents->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'])
2023-10-03 03:02:48 +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-08-22 21:47:10 +12:00
->param('entrypoint', null, new Text(1028), 'Entrypoint File.', true)
->param('commands', null, new Text(8192, 0), 'Build Commands.', true)
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')
2022-12-21 05:11:30 +13:00
->inject('queueForEvents')
->inject('project')
2024-02-21 03:10:51 +13:00
->inject('deviceForFunctions')
->inject('deviceForLocal')
2022-12-21 05:11:30 +13:00
->inject('queueForBuilds')
2024-02-21 03:10:51 +13:00
->action(function (string $functionId, ?string $entrypoint, ?string $commands, mixed $code, bool $activate, Request $request, Response $response, Database $dbForProject, Event $queueForEvents, Document $project, Device $deviceForFunctions, Device $deviceForLocal, Build $queueForBuilds) {
2023-10-02 06:39:26 +13:00
$activate = filter_var($activate, FILTER_VALIDATE_BOOLEAN);
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
}
2020-07-15 08:33:52 +12:00
2023-08-22 21:47:10 +12:00
if ($entrypoint === null) {
$entrypoint = $function->getAttribute('entrypoint', '');
}
if ($commands === null) {
$commands = $function->getAttribute('commands', '');
2023-08-22 21:47:10 +12: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');
2023-06-11 22:29:04 +12: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
}
2023-06-11 22:29:04 +12:00
$fileExt = new FileExt([FileExt::TYPE_GZIP]);
2023-10-03 03:02:48 +13:00
$fileSizeValidator = new FileSize(App::getEnv('_APP_FUNCTIONS_SIZE_LIMIT', '30000000'));
2023-06-11 22:29:04 +12:00
$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
2024-02-21 03:10:51 +13:00
$fileSize ??= $deviceForLocal->getFileSize($fileTmpName);
$path = $deviceForFunctions->getPath($deploymentId . '.' . \pathinfo($fileName, PATHINFO_EXTENSION));
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
2024-02-21 03:10:51 +13:00
$metadata = ['content_type' => $deviceForLocal->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;
}
}
2024-02-21 03:10:51 +13:00
$chunksUploaded = $deviceForFunctions->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);
}
}
2024-02-21 03:10:51 +13:00
$fileSize = $deviceForFunctions->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()),
],
2023-06-11 22:29:04 +12:00
'resourceInternalId' => $function->getInternalId(),
'resourceId' => $function->getId(),
2022-02-19 04:38:48 +13:00
'resourceType' => 'functions',
2023-06-11 22:29:04 +12: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
2022-12-21 05:11:30 +13:00
$queueForBuilds
2022-04-20 01:13:55 +12:00
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
2024-02-21 00:40:55 +13:00
->setDeployment($deployment);
} 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()),
],
2023-06-11 22:29:04 +12:00
'resourceInternalId' => $function->getInternalId(),
'resourceId' => $function->getId(),
2022-02-19 04:38:48 +13:00
'resourceType' => 'functions',
2023-06-11 22:29:04 +12: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;
2022-12-21 05:11:30 +13:00
$queueForEvents
->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'])
2023-10-03 03:02:48 +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
2024-02-13 05:02:04 +13:00
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
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
2024-02-12 22:55:45 +13:00
/**
2024-02-12 23:03:31 +13:00
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
2024-02-12 22:55:45 +13:00
*/
2023-08-22 15:25:55 +12:00
$cursor = \array_filter($queries, function ($query) {
2023-12-07 03:10:40 +13:00
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
2023-08-22 15:25:55 +12:00
});
$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'])
2023-10-03 03:02:48 +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'])
2023-10-03 03:02:48 +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')
2022-12-21 05:11:30 +13:00
->inject('queueForDeletes')
->inject('queueForEvents')
2024-02-21 03:10:51 +13:00
->inject('deviceForFunctions')
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Device $deviceForFunctions) {
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', ''))) {
2024-02-21 03:10:51 +13:00
if (!($deviceForFunctions->delete($deployment->getAttribute('path', '')))) {
2023-06-14 20:57:30 +12:00
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' => '',
2023-06-12 02:08:48 +12:00
'deploymentInternalId' => '',
2021-05-05 09:25:17 +12:00
])));
2020-10-31 08:53:27 +13:00
}
2022-12-21 05:11:30 +13:00
$queueForEvents
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId());
2020-07-19 09:48:28 +12:00
2022-12-21 05:11:30 +13:00
$queueForDeletes
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'])
2023-10-03 03:02:48 +13:00
->desc('Create build')
2022-09-29 21:42:46 +13:00
->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('project')
2022-12-21 05:11:30 +13:00
->inject('queueForEvents')
->inject('queueForBuilds')
->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds) {
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();
2022-09-29 21:42:46 +13:00
2023-09-14 07:18:50 +12:00
$deployment->removeAttribute('$internalId');
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
2022-12-21 05:11:30 +13:00
$queueForBuilds
2023-08-19 18:15:47 +12:00
->setType(BUILD_TYPE_DEPLOYMENT)
2022-09-29 21:42:46 +13:00
->setResource($function)
2024-02-21 00:40:55 +13:00
->setDeployment($deployment);
2022-09-29 21:42:46 +13:00
$queueForEvents
2023-08-18 09:37:52 +12:00
->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'])
2023-10-03 03:02:48 +13: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-10-03 03:02:48 +13:00
->param('body', '', new Text(0, 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-09-06 20:16:01 +12: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')
2022-12-21 05:11:30 +13:00
->inject('queueForEvents')
2023-10-25 20:39:59 +13:00
->inject('queueForUsage')
->inject('mode')
2022-11-16 23:33:11 +13:00
->inject('queueForFunctions')
->inject('geodb')
2023-10-25 20:39:59 +13:00
->action(function (string $functionId, string $body, bool $async, string $path, string $method, array $headers, Response $response, Document $project, Database $dbForProject, Document $user, Event $queueForEvents, Usage $queueForUsage, string $mode, Func $queueForFunctions, Reader $geodb) {
2020-07-17 09:50:37 +12:00
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
2020-07-13 09:18:52 +12:00
2023-08-17 09:58:25 +12:00
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
2023-08-17 09:58:25 +12:00
if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-07-13 09:18:52 +12:00
}
2020-07-17 09:50:37 +12:00
2023-09-05 05:53:25 +12:00
$version = $function->getAttribute('version', 'v2');
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : '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]),
]);
2022-12-21 05:11:30 +13:00
$queueForEvents
->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)
->setParam('functionId', $function->getId())
->setParam('executionId', $execution->getId())
2022-11-17 01:44:14 +13:00
->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);
2022-08-11 01:43:05 +12:00
2023-03-12 05:06:02 +13:00
$vars = [];
2023-09-11 22:22:16 +12:00
// V2 vars
if ($version === 'v2') {
$vars = \array_merge($vars, [
'APPWRITE_FUNCTION_TRIGGER' => $headers['x-appwrite-trigger'] ?? '',
'APPWRITE_FUNCTION_DATA' => $body ?? '',
'APPWRITE_FUNCTION_USER_ID' => $headers['x-appwrite-user-id'] ?? '',
'APPWRITE_FUNCTION_JWT' => $headers['x-appwrite-user-jwt'] ?? ''
]);
}
2023-03-12 05:06:02 +13:00
// Shared vars
2023-09-05 20:21:36 +12:00
foreach ($function->getAttribute('varsProject', []) as $var) {
2023-08-18 18:55:44 +12:00
$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
2023-09-05 20:21:36 +12:00
foreach ($function->getAttribute('vars', []) as $var) {
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
2023-03-12 05:06:02 +13:00
// 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 {
2023-09-05 05:53:25 +12:00
$version = $function->getAttribute('version', 'v2');
$command = $runtime['startCommand'];
2023-09-05 05:53:25 +12:00
$command = $version === 'v2' ? '' : 'cp /tmp/code.tar.gz /mnt/code/code.tar.gz && nohup helpers/start.sh "' . $command . '"';
$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-09-05 05:53:25 +12:00
version: $version,
2023-02-15 00:01:38 +13:00
path: $path,
method: $method,
headers: $headers,
2023-10-16 06:41:09 +13:00
runtimeEntrypoint: $command,
requestTimeout: 30
);
$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']);
} 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());
2023-12-14 17:49:16 +13:00
} finally {
2023-12-13 08:21:47 +13:00
$queueForUsage
->addMetric(METRIC_EXECUTIONS, 1)
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1)
2023-12-14 17:49:16 +13:00
->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
2023-12-13 08:21:47 +13:00
;
}
2022-02-04 14:29:40 +13:00
2023-10-06 02:15:39 +13:00
if ($function->getAttribute('logging')) {
/** @var Document $execution */
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
}
$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'])
2023-10-03 03:02:48 +13: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) {
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
2020-05-06 05:30:12 +12:00
2023-08-17 09:58:25 +12:00
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
2023-08-17 09:58:25 +12:00
if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-07-13 09:18:52 +12:00
}
2021-05-27 22:09:14 +12:00
2024-02-13 05:02:04 +13:00
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
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()]);
2024-02-12 22:55:45 +13:00
/**
2024-02-12 23:03:31 +13:00
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
2024-02-12 22:55:45 +13:00
*/
2023-08-22 15:25:55 +12:00
$cursor = \array_filter($queries, function ($query) {
2023-12-07 03:10:40 +13:00
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
2023-08-22 15:25:55 +12:00
});
$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'])
2023-10-03 03:02:48 +13: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
2023-08-17 09:58:25 +12:00
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
2023-08-17 09:58:25 +12:00
if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
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')
2023-10-03 03:02:48 +13:00
->desc('Create variable')
2022-08-02 03:13:47 +12:00
->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)
2022-08-02 03:13:47 +12:00
->inject('response')
->inject('dbForProject')
2023-06-11 22:29:04 +12:00
->inject('dbForConsole')
->action(function (string $functionId, string $key, string $value, 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
}
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false));
2022-08-02 03:13:47 +12:00
2023-07-28 19:56:07 +12:00
// Inform scheduler to pull the latest changes
2023-06-11 22:29:04 +12: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-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')
2023-10-03 03:02:48 +13:00
->desc('List variables')
2022-08-02 03:13:47 +12:00
->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([
2023-09-05 20:21:36 +12:00
'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')
2023-10-03 03:02:48 +13:00
->desc('Get variable')
2022-08-02 03:13:47 +12:00
->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')
2023-10-03 03:02:48 +13:00
->desc('Update variable')
2022-08-02 03:13:47 +12:00
->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)
2022-08-02 03:13:47 +12:00
->inject('response')
->inject('dbForProject')
2023-06-11 22:29:04 +12:00
->inject('dbForConsole')
->action(function (string $functionId, string $variableId, string $key, ?string $value, 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
}
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false));
2022-08-02 03:13:47 +12:00
2023-07-28 19:56:07 +12:00
// Inform scheduler to pull the latest changes
2023-06-11 22:29:04 +12: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
$response->dynamic($variable, Response::MODEL_VARIABLE);
});
2022-08-10 03:29:24 +12:00
App::delete('/v1/functions/:functionId/variables/:variableId')
2023-10-03 03:02:48 +13:00
->desc('Delete variable')
2022-08-02 03:13:47 +12:00
->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)
2022-08-02 03:13:47 +12:00
->inject('response')
->inject('dbForProject')
2023-06-11 22:29:04 +12:00
->inject('dbForConsole')
->action(function (string $functionId, string $variableId, 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-06-11 22:29:04 +12:00
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false));
2022-08-02 03:13:47 +12:00
2023-07-28 19:56:07 +12:00
// Inform scheduler to pull the latest changes
2023-06-11 22:29:04 +12: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
$response->noContent();
});