appwrite/src/Appwrite/Platform/Workers/Functions.php

633 lines
25 KiB
PHP
Raw Normal View History

2023-06-05 16:13:00 +00:00
<?php
namespace Appwrite\Platform\Workers;
2024-01-29 11:15:07 +00:00
use Ahc\Jwt\JWT;
use Appwrite\Bus\Events\ExecutionCompleted;
2023-06-05 16:13:00 +00:00
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Realtime;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception as AppwriteException;
2023-06-05 16:13:00 +00:00
use Appwrite\Utopia\Response\Model\Execution;
use Executor\Executor;
use Utopia\Bus\Bus;
2023-06-05 16:13:00 +00:00
use Utopia\Config\Config;
use Utopia\Console;
2023-06-05 16:13:00 +00:00
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
2023-09-28 17:37:07 +00:00
use Utopia\Logger\Log;
2023-06-05 16:13:00 +00:00
use Utopia\Platform\Action;
use Utopia\Queue\Message;
2024-04-01 11:02:47 +00:00
use Utopia\System\System;
2023-06-05 16:13:00 +00:00
class Functions extends Action
{
public static function getName(): string
{
return 'functions';
}
/**
* @throws Exception
*/
public function __construct()
{
$this
->desc('Functions worker')
2023-12-12 20:33:02 +00:00
->groups(['functions'])
->inject('project')
2023-06-05 16:13:00 +00:00
->inject('message')
->inject('dbForProject')
->inject('queueForWebhooks')
2023-06-05 16:13:00 +00:00
->inject('queueForFunctions')
->inject('queueForRealtime')
2023-06-11 10:29:04 +00:00
->inject('queueForEvents')
->inject('bus')
2023-09-28 17:37:07 +00:00
->inject('log')
->inject('executor')
->inject('isResourceBlocked')
2025-06-04 08:37:43 +00:00
->callback($this->action(...));
2023-06-05 16:13:00 +00:00
}
public function action(
Document $project,
Message $message,
Database $dbForProject,
Webhook $queueForWebhooks,
Func $queueForFunctions,
Realtime $queueForRealtime,
Event $queueForEvents,
Bus $bus,
Log $log,
2025-04-08 08:41:39 +00:00
Executor $executor,
callable $isResourceBlocked
): void {
2023-06-05 16:13:00 +00:00
$payload = $message->getPayload() ?? [];
if (empty($payload)) {
throw new AppwriteException(
AppwriteException::GENERAL_ARGUMENT_INVALID,
'Functions worker: missing payload in schedule execution'
);
2023-06-05 16:13:00 +00:00
}
$type = $payload['type'] ?? '';
2024-10-11 13:32:48 +00:00
2023-06-05 16:13:00 +00:00
$events = $payload['events'] ?? [];
2023-10-23 13:33:26 +00:00
$data = $payload['body'] ?? '';
2023-06-05 16:13:00 +00:00
$eventData = $payload['payload'] ?? '';
2025-12-16 22:35:39 +00:00
$platform = $payload['platform'] ?? Config::getParam('platform', []);
2023-06-05 16:13:00 +00:00
$function = new Document($payload['function'] ?? []);
2024-06-17 12:44:12 +00:00
$functionId = $payload['functionId'] ?? '';
2023-06-05 16:13:00 +00:00
$user = new Document($payload['user'] ?? []);
2024-09-07 10:20:23 +00:00
$userId = $payload['userId'] ?? '';
2023-09-28 17:37:07 +00:00
$method = $payload['method'] ?? 'POST';
$headers = $payload['headers'] ?? [];
$path = $payload['path'] ?? '/';
2024-09-07 10:20:23 +00:00
$jwt = $payload['jwt'] ?? '';
if ($user->isEmpty() && !empty($userId)) {
$user = $dbForProject->getDocument('users', $userId);
}
if (empty($jwt) && !$user->isEmpty()) {
2025-08-15 10:29:34 +00:00
$jwtExpiry = $function->getAttribute('timeout', 900) + 60; // 1min extra to account for possible cold-starts
2024-09-07 10:20:23 +00:00
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
$jwt = $jwtObj->encode([
'userId' => $user->getId(),
]);
}
2023-06-05 16:13:00 +00:00
if ($project->getId() === 'console') {
return;
}
2024-06-17 12:44:12 +00:00
if ($function->isEmpty() && !empty($functionId)) {
$function = $dbForProject->getDocument('functions', $functionId);
}
$log->addTag('functionId', $function->getId());
$log->addTag('projectId', $project->getId());
$log->addTag('type', $type);
2023-06-05 16:13:00 +00:00
if (!empty($events)) {
2025-12-29 08:20:32 +00:00
$limit = 100;
$sum = 100;
2023-06-05 16:13:00 +00:00
$offset = 0;
while ($sum >= $limit) {
$functions = $dbForProject->find('functions', [
2025-12-29 08:20:32 +00:00
Query::select(['$id', 'events']), // Skip variables subqueries
2025-12-29 09:55:21 +00:00
Query::contains('events', $events),
2023-06-05 16:13:00 +00:00
Query::limit($limit),
Query::offset($offset),
2025-12-29 08:20:32 +00:00
Query::orderAsc('$sequence'),
2023-06-05 16:13:00 +00:00
]);
$sum = \count($functions);
$offset = $offset + $limit;
Console::log('Fetched ' . $sum . ' functions...');
foreach ($functions as $function) {
if (!array_intersect($events, $function->getAttribute('events', []))) {
continue;
}
2024-10-29 15:07:12 +00:00
if ($isResourceBlocked($project, RESOURCE_TYPE_FUNCTIONS, $function->getId())) {
Console::log('Function ' . $function->getId() . ' is blocked, skipping execution.');
continue;
}
2025-12-29 08:20:32 +00:00
/**
* get variables subqueries cached
*/
$function = $dbForProject->getDocument('functions', $function->getId());
2023-06-05 16:13:00 +00:00
Console::success('Iterating function: ' . $function->getAttribute('name'));
$this->execute(
2023-09-28 17:37:07 +00:00
log: $log,
2023-06-05 16:13:00 +00:00
dbForProject: $dbForProject,
queueForWebhooks: $queueForWebhooks,
2023-06-05 16:13:00 +00:00
queueForFunctions: $queueForFunctions,
queueForRealtime: $queueForRealtime,
2023-09-28 17:37:07 +00:00
queueForEvents: $queueForEvents,
bus: $bus,
2023-06-05 16:13:00 +00:00
project: $project,
function: $function,
executor: $executor,
2023-06-05 16:13:00 +00:00
trigger: 'event',
2023-09-28 17:37:07 +00:00
path: '/',
method: 'POST',
headers: [
2023-10-17 16:23:10 +00:00
'user-agent' => 'Appwrite/' . APP_VERSION_STABLE,
'content-type' => 'application/json'
2023-09-28 17:37:07 +00:00
],
2025-12-07 20:29:45 +00:00
platform: $platform,
2023-09-28 17:37:07 +00:00
data: null,
2023-06-05 16:13:00 +00:00
user: $user,
2023-09-28 17:37:07 +00:00
jwt: null,
2023-06-05 16:13:00 +00:00
event: $events[0],
eventData: \is_string($eventData) ? $eventData : \json_encode($eventData),
2023-09-28 17:37:07 +00:00
executionId: null,
2023-06-05 16:13:00 +00:00
);
Console::success('Triggered function: ' . $events[0]);
}
}
return;
}
2024-10-29 15:07:12 +00:00
if ($isResourceBlocked($project, RESOURCE_TYPE_FUNCTIONS, $function->getId())) {
Console::log('Function ' . $function->getId() . ' is blocked, skipping execution.');
return;
}
2023-06-05 16:13:00 +00:00
/**
* Handle Schedule and HTTP execution.
*/
switch ($type) {
case 'http':
$execution = new Document($payload['execution'] ?? []);
$user = new Document($payload['user'] ?? []);
$this->execute(
2023-09-28 17:37:07 +00:00
log: $log,
2023-06-05 16:13:00 +00:00
dbForProject: $dbForProject,
queueForWebhooks: $queueForWebhooks,
2023-06-05 16:13:00 +00:00
queueForFunctions: $queueForFunctions,
queueForRealtime: $queueForRealtime,
2023-09-28 17:37:07 +00:00
queueForEvents: $queueForEvents,
bus: $bus,
2023-06-05 16:13:00 +00:00
project: $project,
function: $function,
executor: $executor,
2023-06-05 16:13:00 +00:00
trigger: 'http',
2023-09-28 17:37:07 +00:00
path: $path,
method: $method,
headers: $headers,
2025-12-07 20:29:45 +00:00
platform: $platform,
2023-06-05 16:13:00 +00:00
data: $data,
user: $user,
jwt: $jwt,
2023-09-28 17:37:07 +00:00
event: null,
eventData: null,
executionId: $execution->getId()
2023-06-05 16:13:00 +00:00
);
break;
case 'schedule':
2024-06-17 12:44:12 +00:00
$execution = new Document($payload['execution'] ?? []);
2023-06-05 16:13:00 +00:00
$this->execute(
2023-09-28 17:37:07 +00:00
log: $log,
2023-06-05 16:13:00 +00:00
dbForProject: $dbForProject,
queueForWebhooks: $queueForWebhooks,
2023-06-05 16:13:00 +00:00
queueForFunctions: $queueForFunctions,
queueForRealtime: $queueForRealtime,
2023-09-28 17:37:07 +00:00
queueForEvents: $queueForEvents,
bus: $bus,
2023-06-05 16:13:00 +00:00
project: $project,
function: $function,
executor: $executor,
2023-06-05 16:13:00 +00:00
trigger: 'schedule',
2023-09-28 17:37:07 +00:00
path: $path,
method: $method,
headers: $headers,
2025-12-07 20:29:45 +00:00
platform: $platform,
2024-09-07 10:20:23 +00:00
data: $data,
user: $user,
jwt: $jwt,
2023-09-28 17:37:07 +00:00
event: null,
eventData: null,
2024-06-17 12:44:12 +00:00
executionId: $execution->getId() ?? null
2023-06-05 16:13:00 +00:00
);
break;
}
}
/**
* @param string $message
* @param Document $function
* @param string $trigger
* @param string $path
* @param string $method
* @param Document $user
2024-04-01 11:48:42 +00:00
* @param string|null $jwt
* @param string|null $event
* @throws Exception
*/
private function fail(
string $message,
2026-02-09 19:24:57 +00:00
Document $project,
Bus $bus,
Document $function,
string $trigger,
string $path,
string $method,
Document $user,
2026-02-03 04:19:37 +00:00
?string $jwt = null,
?string $event = null,
): void {
$executionId = ID::unique();
$headers['x-appwrite-execution-id'] = $executionId ?? '';
2024-04-01 11:48:42 +00:00
$headers['x-appwrite-trigger'] = $trigger;
$headers['x-appwrite-event'] = $event ?? '';
$headers['x-appwrite-user-id'] = $user->getId() ?? '';
$headers['x-appwrite-user-jwt'] = $jwt ?? '';
$headersFiltered = [];
foreach ($headers as $key => $value) {
if (\in_array(\strtolower($key), FUNCTION_ALLOWLIST_HEADERS_REQUEST)) {
$headersFiltered[] = ['name' => $key, 'value' => $value];
}
}
$execution = new Document([
'$id' => $executionId,
'$permissions' => $user->isEmpty() ? [] : [Permission::read(Role::user($user->getId()))],
'resourceInternalId' => $function->getSequence(),
2024-12-02 13:00:06 +00:00
'resourceId' => $function->getId(),
'resourceType' => 'functions',
'deploymentInternalId' => '',
'deploymentId' => '',
'trigger' => $trigger,
'status' => 'failed',
2024-04-01 14:07:51 +00:00
'responseStatusCode' => 0,
'responseHeaders' => [],
'requestPath' => $path,
'requestMethod' => $method,
2024-04-01 11:48:42 +00:00
'requestHeaders' => $headersFiltered,
'errors' => $message,
'logs' => '',
'duration' => 0.0,
]);
$bus->dispatch(new ExecutionCompleted(
execution: $execution->getArrayCopy(),
project: $project->getArrayCopy(),
));
}
2023-06-05 16:13:00 +00:00
/**
2023-10-01 17:39:26 +00:00
* @param Log $log
* @param Database $dbForProject
* @param Func $queueForFunctions
* @param Realtime $queueForRealtime
2023-10-01 17:39:26 +00:00
* @param Event $queueForEvents
* @param Document $project
* @param Document $function
* @param Executor $executor
2023-10-01 17:39:26 +00:00
* @param string $trigger
* @param string $path
* @param string $method
* @param array $headers
* @param string|null $data
* @param Document|null $user
* @param string|null $jwt
* @param string|null $event
* @param string|null $eventData
* @param string|null $executionId
* @return void
2023-06-05 16:13:00 +00:00
*/
private function execute(
2023-09-28 17:37:07 +00:00
Log $log,
2023-06-05 16:13:00 +00:00
Database $dbForProject,
Webhook $queueForWebhooks,
2023-06-05 16:13:00 +00:00
Func $queueForFunctions,
Realtime $queueForRealtime,
2023-09-28 17:37:07 +00:00
Event $queueForEvents,
Bus $bus,
2023-06-05 16:13:00 +00:00
Document $project,
Document $function,
Executor $executor,
2023-06-05 16:13:00 +00:00
string $trigger,
2023-09-28 17:37:07 +00:00
string $path,
string $method,
array $headers,
2025-12-07 20:29:45 +00:00
array $platform,
2026-02-03 04:19:37 +00:00
?string $data = null,
2023-06-05 16:13:00 +00:00
?Document $user = null,
2026-02-03 04:19:37 +00:00
?string $jwt = null,
?string $event = null,
?string $eventData = null,
?string $executionId = null,
2023-06-05 16:13:00 +00:00
): void {
$user ??= new Document();
$functionId = $function->getId();
2025-03-11 15:26:49 +00:00
$deploymentId = $function->getAttribute('deploymentId', '');
2026-03-04 13:31:05 +00:00
$spec = Config::getParam('specifications')[$function->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
2023-06-05 16:13:00 +00:00
$log->addTag('deploymentId', $deploymentId);
2023-09-28 17:37:07 +00:00
/** Check if deployment exists */
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
2023-06-05 16:13:00 +00:00
if ($deployment->getAttribute('resourceId') !== $functionId) {
$errorMessage = 'The execution could not be completed because a corresponding deployment was not found. A function deployment needs to be created before it can be executed. Please create a deployment for your function and try again.';
$this->fail($errorMessage, $project, $bus, $function, $trigger, $path, $method, $user, $jwt, $event);
return;
2023-06-05 16:13:00 +00:00
}
if ($deployment->isEmpty()) {
$errorMessage = 'The execution could not be completed because a corresponding deployment was not found. A function deployment needs to be created before it can be executed. Please create a deployment for your function and try again.';
$this->fail($errorMessage, $project, $bus, $function, $trigger, $path, $method, $user, $jwt, $event);
return;
2023-06-05 16:13:00 +00:00
}
if ($deployment->getAttribute('status') !== 'ready') {
$errorMessage = 'The execution could not be completed because the build is not ready. Please wait for the build to complete and try again.';
$this->fail($errorMessage, $project, $bus, $function, $trigger, $path, $method, $user, $jwt, $event);
return;
2023-06-05 16:13:00 +00:00
}
/** Check if runtime is supported */
2023-09-28 17:37:07 +00:00
$version = $function->getAttribute('version', 'v2');
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
2023-06-05 16:13:00 +00:00
if (!\array_key_exists($function->getAttribute('runtime'), $runtimes)) {
throw new AppwriteException(
AppwriteException::FUNCTION_RUNTIME_UNSUPPORTED,
\sprintf('Runtime "%s" is not supported', $function->getAttribute('runtime', '')),
);
2023-06-05 16:13:00 +00:00
}
2023-09-28 17:37:07 +00:00
$runtime = $runtimes[$function->getAttribute('runtime')];
2023-06-05 16:13:00 +00:00
2025-08-15 10:29:34 +00:00
$jwtExpiry = $function->getAttribute('timeout', 900) + 60; // 1min extra to account for possible cold-starts
2024-05-29 07:51:51 +00:00
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
2024-05-06 09:55:59 +00:00
$apiKey = $jwtObj->encode([
2024-01-29 11:15:07 +00:00
'projectId' => $project->getId(),
2024-05-06 09:55:59 +00:00
'scopes' => $function->getAttribute('scopes', [])
2024-01-29 11:15:07 +00:00
]);
$headers['x-appwrite-execution-id'] = $executionId ?? '';
2024-05-14 11:58:31 +00:00
$headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $apiKey;
2023-09-28 17:37:07 +00:00
$headers['x-appwrite-trigger'] = $trigger;
$headers['x-appwrite-event'] = $event ?? '';
$headers['x-appwrite-user-id'] = $user->getId() ?? '';
$headers['x-appwrite-user-jwt'] = $jwt ?? '';
2024-07-23 12:28:13 +00:00
$headers['x-appwrite-country-code'] = '';
$headers['x-appwrite-continent-code'] = '';
$headers['x-appwrite-continent-eu'] = 'false';
2023-09-28 17:37:07 +00:00
2026-02-09 19:24:57 +00:00
/** Create or update execution to processing status */
if (empty($executionId)) {
$executionId = ID::unique();
2023-06-05 16:13:00 +00:00
}
2026-02-09 19:24:57 +00:00
$headers['x-appwrite-execution-id'] = $executionId;
2023-06-05 16:13:00 +00:00
2026-02-09 19:24:57 +00:00
$headersFiltered = [];
foreach ($headers as $key => $value) {
if (\in_array(\strtolower($key), FUNCTION_ALLOWLIST_HEADERS_REQUEST)) {
$headersFiltered[] = [ 'name' => $key, 'value' => $value ];
2023-06-05 16:13:00 +00:00
}
}
2026-02-09 19:24:57 +00:00
$execution = new Document([
'$id' => $executionId,
'$permissions' => $user->isEmpty() ? [] : [Permission::read(Role::user($user->getId()))],
'resourceInternalId' => $function->getSequence(),
'resourceId' => $function->getId(),
'resourceType' => 'functions',
'deploymentInternalId' => $deployment->getSequence(),
'deploymentId' => $deployment->getId(),
'trigger' => $trigger,
'status' => 'processing',
'responseStatusCode' => 0,
'responseHeaders' => [],
'requestPath' => $path,
'requestMethod' => $method,
'requestHeaders' => $headersFiltered,
'errors' => '',
'logs' => '',
'duration' => 0.0,
]);
2023-09-28 17:37:07 +00:00
$durationStart = \microtime(true);
2023-06-05 16:13:00 +00:00
2023-09-28 17:37:07 +00:00
$body = $eventData ?? '';
if (empty($body)) {
$body = $data ?? '';
}
$vars = [];
// V2 vars
if ($version === 'v2') {
2023-06-05 16:13:00 +00:00
$vars = \array_merge($vars, [
2023-09-28 17:37:07 +00:00
'APPWRITE_FUNCTION_TRIGGER' => $headers['x-appwrite-trigger'] ?? '',
'APPWRITE_FUNCTION_DATA' => $body ?? '',
'APPWRITE_FUNCTION_EVENT_DATA' => $body ?? '',
'APPWRITE_FUNCTION_EVENT' => $headers['x-appwrite-event'] ?? '',
'APPWRITE_FUNCTION_USER_ID' => $headers['x-appwrite-user-id'] ?? '',
'APPWRITE_FUNCTION_JWT' => $headers['x-appwrite-user-jwt'] ?? ''
2023-06-05 16:13:00 +00:00
]);
2023-09-28 17:37:07 +00:00
}
// Shared vars
foreach ($function->getAttribute('varsProject', []) as $var) {
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
2023-06-05 16:13:00 +00:00
2023-09-28 17:37:07 +00:00
// Function vars
foreach ($function->getAttribute('vars', []) as $var) {
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
2024-05-06 11:27:28 +00:00
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
2025-12-16 22:35:39 +00:00
$endpoint = "$protocol://{$platform['apiHostname']}/v1";
2024-01-29 11:15:07 +00:00
2023-09-28 17:37:07 +00:00
// Appwrite vars
$vars = \array_merge($vars, [
2024-05-09 11:50:45 +00:00
'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint,
2023-09-28 17:37:07 +00:00
'APPWRITE_FUNCTION_ID' => $functionId,
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'),
'APPWRITE_FUNCTION_DEPLOYMENT' => $deploymentId,
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
'APPWRITE_FUNCTION_CPUS' => ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT),
'APPWRITE_FUNCTION_MEMORY' => ($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT),
2024-07-15 07:10:11 +00:00
'APPWRITE_VERSION' => APP_VERSION_STABLE,
'APPWRITE_REGION' => $project->getAttribute('region'),
'APPWRITE_DEPLOYMENT_TYPE' => $deployment->getAttribute('type', ''),
'APPWRITE_VCS_REPOSITORY_ID' => $deployment->getAttribute('providerRepositoryId', ''),
'APPWRITE_VCS_REPOSITORY_NAME' => $deployment->getAttribute('providerRepositoryName', ''),
'APPWRITE_VCS_REPOSITORY_OWNER' => $deployment->getAttribute('providerRepositoryOwner', ''),
'APPWRITE_VCS_REPOSITORY_URL' => $deployment->getAttribute('providerRepositoryUrl', ''),
'APPWRITE_VCS_REPOSITORY_BRANCH' => $deployment->getAttribute('providerBranch', ''),
'APPWRITE_VCS_REPOSITORY_BRANCH_URL' => $deployment->getAttribute('providerBranchUrl', ''),
'APPWRITE_VCS_COMMIT_HASH' => $deployment->getAttribute('providerCommitHash', ''),
'APPWRITE_VCS_COMMIT_MESSAGE' => $deployment->getAttribute('providerCommitMessage', ''),
'APPWRITE_VCS_COMMIT_URL' => $deployment->getAttribute('providerCommitUrl', ''),
'APPWRITE_VCS_COMMIT_AUTHOR_NAME' => $deployment->getAttribute('providerCommitAuthor', ''),
'APPWRITE_VCS_COMMIT_AUTHOR_URL' => $deployment->getAttribute('providerCommitAuthorUrl', ''),
'APPWRITE_VCS_ROOT_DIRECTORY' => $deployment->getAttribute('providerRootDirectory', ''),
2023-09-28 17:37:07 +00:00
]);
/** Execute function */
2023-06-05 16:13:00 +00:00
try {
2023-09-28 17:37:07 +00:00
$version = $function->getAttribute('version', 'v2');
$command = $runtime['startCommand'];
2025-06-13 10:15:32 +00:00
$source = $deployment->getAttribute('buildPath', '');
$extension = str_ends_with($source, '.tar') ? 'tar' : 'tar.gz';
$command = $version === 'v2' ? '' : "cp /tmp/code.$extension /mnt/code/code.$extension && nohup helpers/start.sh \"$command\"";
2023-09-28 17:37:07 +00:00
$executionResponse = $executor->createExecution(
2023-06-05 16:13:00 +00:00
projectId: $project->getId(),
deploymentId: $deploymentId,
2023-09-28 17:37:07 +00:00
body: \strlen($body) > 0 ? $body : null,
2023-06-05 16:13:00 +00:00
variables: $vars,
timeout: $function->getAttribute('timeout', 0),
image: $runtime['image'],
2025-06-13 10:15:32 +00:00
source: $source,
2023-06-05 16:13:00 +00:00
entrypoint: $deployment->getAttribute('entrypoint', ''),
2023-09-28 17:37:07 +00:00
version: $version,
path: $path,
method: $method,
headers: $headers,
2024-07-12 09:25:57 +00:00
runtimeEntrypoint: $command,
cpus: $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
2024-06-25 09:33:07 +00:00
logging: $function->getAttribute('logging', true),
2023-06-05 16:13:00 +00:00
);
2024-08-08 08:38:15 +00:00
$status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed';
2023-09-28 17:37:07 +00:00
2025-08-26 13:26:26 +00:00
$executionResponse['headers']['x-appwrite-execution-id'] = $execution->getId();
2023-09-28 17:37:07 +00:00
$headersFiltered = [];
foreach ($executionResponse['headers'] as $key => $value) {
if (\in_array(\strtolower($key), FUNCTION_ALLOWLIST_HEADERS_RESPONSE)) {
$headersFiltered[] = [ 'name' => $key, 'value' => $value ];
}
}
2025-08-20 13:23:55 +00:00
$maxLogLength = APP_FUNCTION_LOG_LENGTH_LIMIT;
2025-05-16 10:10:59 +00:00
$logs = $executionResponse['logs'] ?? '';
if (\is_string($logs) && \strlen($logs) > $maxLogLength) {
$warningMessage = "[WARNING] Logs truncated. The output exceeded {$maxLogLength} characters.\n";
2025-08-20 13:23:55 +00:00
$warningLength = \strlen($warningMessage);
$maxContentLength = $maxLogLength - $warningLength;
$logs = $warningMessage . \substr($logs, -$maxContentLength);
}
// Truncate errors if they exceed the limit
$maxErrorLength = APP_FUNCTION_ERROR_LENGTH_LIMIT;
$errors = $executionResponse['errors'] ?? '';
if (\is_string($errors) && \strlen($errors) > $maxErrorLength) {
$warningMessage = "[WARNING] Errors truncated. The output exceeded {$maxErrorLength} characters.\n";
$warningLength = \strlen($warningMessage);
$maxContentLength = $maxErrorLength - $warningLength;
$errors = $warningMessage . \substr($errors, -$maxContentLength);
2025-05-16 10:10:59 +00:00
}
2023-09-28 17:37:07 +00:00
2023-06-05 16:13:00 +00:00
/** Update execution status */
$execution
2023-09-28 17:37:07 +00:00
->setAttribute('status', $status)
->setAttribute('responseStatusCode', $executionResponse['statusCode'])
->setAttribute('responseHeaders', $headersFiltered)
2025-05-16 10:10:59 +00:00
->setAttribute('logs', $logs)
->setAttribute('errors', $errors)
2023-06-05 16:13:00 +00:00
->setAttribute('duration', $executionResponse['duration']);
2025-05-16 10:10:59 +00:00
2023-06-05 16:13:00 +00:00
} catch (\Throwable $th) {
2023-09-28 17:37:07 +00:00
$durationEnd = \microtime(true);
2023-06-05 16:13:00 +00:00
$execution
2023-09-28 17:37:07 +00:00
->setAttribute('duration', $durationEnd - $durationStart)
2023-06-05 16:13:00 +00:00
->setAttribute('status', 'failed')
2023-09-28 17:37:07 +00:00
->setAttribute('responseStatusCode', 500)
->setAttribute('errors', $th->getMessage() . '\nError Code: ' . $th->getCode());
2023-06-05 16:13:00 +00:00
2023-09-28 17:37:07 +00:00
$error = $th->getMessage();
$errorCode = $th->getCode();
2023-12-14 04:49:16 +00:00
} finally {
/** Persist final execution status and record usage */
$bus->dispatch(new ExecutionCompleted(
execution: $execution->getArrayCopy(),
project: $project->getArrayCopy(),
spec: $spec,
));
2023-06-05 16:13:00 +00:00
}
2023-09-28 17:37:07 +00:00
$executionModel = new Execution();
2025-03-13 13:08:14 +00:00
$realtimeExecution = $executionModel->filter(new Document($execution->getArrayCopy()));
$realtimeExecution = $realtimeExecution->getArrayCopy(\array_keys($executionModel->getRules()));
2023-09-28 17:37:07 +00:00
$queueForEvents
->setProject($project)
->setUser($user)
->setEvent('functions.[functionId].executions.[executionId].update')
->setParam('functionId', $function->getId())
->setParam('executionId', $execution->getId())
2025-03-13 13:08:14 +00:00
->setPayload($realtimeExecution);
/** Trigger Webhook */
$queueForWebhooks
->from($queueForEvents)
2023-09-28 17:37:07 +00:00
->trigger();
/** Trigger Functions */
$queueForFunctions
->from($queueForEvents)
->trigger();
/** Trigger Realtime Events */
$queueForRealtime
2025-02-27 07:23:13 +00:00
->setSubscribers(['console', $project->getId()])
2025-03-13 13:08:14 +00:00
->from($queueForEvents)
->trigger();
2023-09-28 17:37:07 +00:00
if (!empty($error)) {
throw new AppwriteException(
AppwriteException::GENERAL_SERVER_ERROR,
'Function execution failed: ' . ($error ?: 'No error message provided'),
$errorCode
);
2023-09-28 17:37:07 +00:00
}
2023-06-05 16:13:00 +00:00
}
}