appwrite/app/controllers/api/functions.php

1902 lines
82 KiB
PHP
Raw Normal View History

2020-05-04 14:35:01 +00:00
<?php
2021-03-10 17:48:05 +00:00
use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
2022-04-19 13:13:55 +00:00
use Appwrite\Event\Build;
2022-05-24 14:28:27 +00:00
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
2022-11-15 18:13:17 +00:00
use Appwrite\Event\Func;
2023-10-25 09:46:45 +00:00
use Appwrite\Event\Usage;
2022-02-18 12:36:24 +00:00
use Appwrite\Extend\Exception;
2024-05-20 10:44:08 +00:00
use Appwrite\Extend\Exception as AppwriteException;
2024-08-06 08:19:28 +00:00
use Appwrite\Functions\Validator\Headers;
use Appwrite\Platform\Tasks\ScheduleExecutions;
2025-02-03 09:32:01 +00:00
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\MethodType;
use Appwrite\SDK\Response as SDKResponse;
2024-03-06 17:34:21 +00:00
use Appwrite\Utopia\Database\Validator\Queries\Deployments;
use Appwrite\Utopia\Database\Validator\Queries\Executions;
use Appwrite\Utopia\Response;
use Executor\Executor;
use MaxMind\Db\Reader;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate as DuplicateException;
2024-02-12 16:02:04 +00:00
use Utopia\Database\Exception\Query as QueryException;
2023-06-11 10:29:04 +00:00
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
2024-03-06 17:34:21 +00:00
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
2024-06-07 19:05:29 +00:00
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Query\Cursor;
2021-07-25 14:47:18 +00:00
use Utopia\Database\Validator\UID;
2022-05-24 14:28:27 +00:00
use Utopia\Storage\Device;
use Utopia\Swoole\Request;
2024-04-01 11:08:46 +00:00
use Utopia\System\System;
2024-08-05 13:08:41 +00:00
use Utopia\Validator\AnyOf;
2020-05-05 17:30:12 +00:00
use Utopia\Validator\ArrayList;
2024-03-06 17:34:21 +00:00
use Utopia\Validator\Assoc;
use Utopia\Validator\Boolean;
2020-05-04 14:35:01 +00:00
use Utopia\Validator\Range;
2024-03-06 17:34:21 +00:00
use Utopia\Validator\Text;
2020-05-05 17:30:12 +00:00
use Utopia\Validator\WhiteList;
2020-05-04 14:35:01 +00:00
include_once __DIR__ . '/../shared/api.php';
App::get('/v1/functions/specifications')
2024-07-12 09:25:57 +00:00
->groups(['api', 'functions'])
2024-08-20 04:23:11 +00:00
->desc('List available function runtime specifications')
2024-07-12 09:25:57 +00:00
->label('scope', 'functions.read')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2025-02-03 09:32:01 +00:00
->label('sdk', new Method(
namespace: 'functions',
name: 'listSpecifications',
description: '/docs/references/functions/list-specifications.md',
auth: [AuthType::KEY, AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_SPECIFICATION_LIST,
)
]
))
2024-07-12 09:25:57 +00:00
->inject('response')
->inject('plan')
->action(function (Response $response, array $plan) {
$allRuntimeSpecs = Config::getParam('runtime-specifications', []);
2024-07-31 06:21:07 +00:00
$runtimeSpecs = [];
foreach ($allRuntimeSpecs as $spec) {
$spec['enabled'] = true;
2024-07-31 06:21:07 +00:00
if (array_key_exists('runtimeSpecifications', $plan)) {
2024-08-02 08:00:42 +00:00
$spec['enabled'] = in_array($spec['slug'], $plan['runtimeSpecifications']);
}
2024-07-31 06:21:07 +00:00
// Only add specs that are within the limits set by environment variables
if ($spec['cpus'] <= System::getEnv('_APP_COMPUTE_CPUS', 1) && $spec['memory'] <= System::getEnv('_APP_COMPUTE_MEMORY', 512)) {
$runtimeSpecs[] = $spec;
2024-07-31 06:21:07 +00:00
}
}
2024-07-12 09:25:57 +00:00
$response->dynamic(new Document([
'specifications' => $runtimeSpecs,
2024-08-19 04:18:45 +00:00
'total' => count($runtimeSpecs)
]), Response::MODEL_SPECIFICATION_LIST);
2024-07-12 09:25:57 +00:00
});
2020-07-12 21:18:52 +00:00
App::get('/v1/functions/:functionId')
->groups(['api', 'functions'])
2023-10-02 14:02:48 +00:00
->desc('Get function')
2020-05-05 17:30:12 +00:00
->label('scope', 'functions.read')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2025-02-03 09:32:01 +00:00
->label('sdk', new Method(
namespace: 'functions',
name: 'get',
description: '/docs/references/functions/get-function.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_FUNCTION,
)
]
))
->param('functionId', '', new UID(), 'Function ID.')
2020-12-26 15:20:08 +00:00
->inject('response')
->inject('dbForProject')
2022-05-24 14:28:27 +00:00
->action(function (string $functionId, Response $response, Database $dbForProject) {
$function = $dbForProject->getDocument('functions', $functionId);
2020-05-05 17:30:12 +00:00
2021-06-20 13:59:36 +00:00
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-05-05 17:30:12 +00:00
}
2020-10-30 19:53:27 +00:00
$response->dynamic($function, Response::MODEL_FUNCTION);
2020-12-26 15:20:08 +00:00
});
2020-07-12 21:18:52 +00:00
2020-10-30 19:53:27 +00:00
App::get('/v1/functions/:functionId/usage')
2023-10-02 14:02:48 +00:00
->desc('Get function usage')
2023-09-27 17:10:21 +00:00
->groups(['api', 'functions', 'usage'])
2020-10-30 19:53:27 +00:00
->label('scope', 'functions.read')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2025-02-03 09:32:01 +00:00
->label('sdk', new Method(
namespace: 'functions',
name: 'getFunctionUsage',
description: '/docs/references/functions/get-function-usage.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_USAGE_FUNCTION,
)
]
))
->param('functionId', '', new UID(), 'Function ID.')
2023-11-08 09:09:32 +00:00
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
2020-12-26 15:20:08 +00:00
->inject('response')
->inject('dbForProject')
2022-05-24 14:28:27 +00:00
->action(function (string $functionId, string $range, Response $response, Database $dbForProject) {
2020-10-30 19:53:27 +00:00
$function = $dbForProject->getDocument('functions', $functionId);
2020-10-30 19:53:27 +00:00
2021-06-20 13:59:36 +00:00
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-10-30 19:53:27 +00:00
}
2023-10-25 07:39:59 +00: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),
2024-07-30 08:53:28 +00:00
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_MB_SECONDS),
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS)
2023-10-25 07:39:59 +00:00
];
2023-11-08 09:09:32 +00:00
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $metric) {
2024-02-01 10:21:50 +00:00
$result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
2023-11-08 09:09:32 +00:00
$stats[$metric]['total'] = $result['value'] ?? 0;
2023-10-25 07:39:59 +00:00
$limit = $days['limit'];
$period = $days['period'];
2024-02-01 10:21:50 +00:00
$results = $dbForProject->find('stats', [
2023-10-25 07:39:59 +00:00
Query::equal('metric', [$metric]),
2023-10-25 12:06:54 +00:00
Query::equal('period', [$period]),
2023-10-25 07:39:59 +00:00
Query::limit($limit),
Query::orderDesc('time'),
]);
2023-11-08 09:09:32 +00:00
$stats[$metric]['data'] = [];
2023-10-25 07:39:59 +00:00
foreach ($results as $result) {
2023-11-08 09:09:32 +00:00
$stats[$metric]['data'][$result->getAttribute('time')] = [
'value' => $result->getAttribute('value'),
2023-10-25 07:39:59 +00:00
];
}
}
});
2023-06-11 10:29:04 +00:00
2023-10-25 07:39:59 +00:00
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
};
2024-03-06 17:34:21 +00: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 10:29:04 +00:00
}
2023-10-25 07:39:59 +00:00
$response->dynamic(new Document([
'range' => $range,
2023-11-08 09:09:32 +00: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'],
2024-07-30 08:53:28 +00:00
'buildsMbSecondsTotal' => $usage[$metrics[7]]['total'],
'buildsMbSeconds' => $usage[$metrics[7]]['data'],
'executionsMbSeconds' => $usage[$metrics[8]]['data'],
'executionsMbSecondsTotal' => $usage[$metrics[8]]['total']
2023-10-25 07:39:59 +00:00
]), Response::MODEL_USAGE_FUNCTION);
2022-07-17 10:30:58 +00:00
});
App::get('/v1/functions/usage')
2023-10-03 16:50:48 +00:00
->desc('Get functions usage')
2024-10-24 13:21:40 +00:00
->groups(['api', 'functions', 'usage'])
2022-07-17 10:30:58 +00:00
->label('scope', 'functions.read')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2025-02-03 09:32:01 +00:00
->label('sdk', new Method(
namespace: 'functions',
name: 'getUsage',
description: '/docs/references/functions/get-functions-usage.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_USAGE_FUNCTIONS,
)
]
))
2023-11-08 09:09:32 +00:00
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
2022-07-17 10:30:58 +00:00
->inject('response')
->inject('dbForProject')
2022-07-17 10:39:44 +00:00
->action(function (string $range, Response $response, Database $dbForProject) {
2022-07-17 10:30:58 +00:00
2023-10-25 07:39:59 +00: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,
2024-07-30 08:53:28 +00:00
METRIC_BUILDS_MB_SECONDS,
METRIC_EXECUTIONS_MB_SECONDS,
2023-10-25 07:39:59 +00:00
];
2023-11-08 09:09:32 +00:00
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $metric) {
2024-02-01 10:21:50 +00:00
$result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
Query::equal('period', ['inf'])
]);
2023-11-08 09:09:32 +00:00
$stats[$metric]['total'] = $result['value'] ?? 0;
2023-10-25 07:39:59 +00:00
$limit = $days['limit'];
$period = $days['period'];
2024-02-01 10:21:50 +00:00
$results = $dbForProject->find('stats', [
2023-10-25 07:39:59 +00:00
Query::equal('metric', [$metric]),
2023-10-25 12:06:54 +00:00
Query::equal('period', [$period]),
2023-10-25 07:39:59 +00:00
Query::limit($limit),
Query::orderDesc('time'),
]);
2023-11-08 09:09:32 +00:00
$stats[$metric]['data'] = [];
2023-10-25 07:39:59 +00:00
foreach ($results as $result) {
2023-11-08 09:09:32 +00:00
$stats[$metric]['data'][$result->getAttribute('time')] = [
'value' => $result->getAttribute('value'),
2023-10-25 07:39:59 +00:00
];
}
}
});
2023-06-11 10:29:04 +00:00
2023-10-25 07:39:59 +00:00
$format = match ($days['period']) {
'1h' => 'Y-m-d\TH:00:00.000P',
'1d' => 'Y-m-d\T00:00:00.000P',
};
2024-03-06 17:34:21 +00: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 10:29:04 +00:00
}
2023-10-25 07:39:59 +00:00
$response->dynamic(new Document([
'range' => $range,
2023-11-08 09:09:32 +00: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'],
2024-07-30 08:53:28 +00:00
'buildsMbSecondsTotal' => $usage[$metrics[8]]['total'],
'buildsMbSeconds' => $usage[$metrics[8]]['data'],
'executionsMbSeconds' => $usage[$metrics[9]]['data'],
'executionsMbSecondsTotal' => $usage[$metrics[9]]['total'],
2023-10-25 07:39:59 +00:00
]), Response::MODEL_USAGE_FUNCTIONS);
2020-12-26 15:20:08 +00:00
});
2020-10-30 19:53:27 +00:00
2023-08-21 10:21:18 +00:00
App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
->groups(['api', 'functions'])
2024-07-01 19:35:55 +00:00
->desc('Download deployment')
2023-08-21 10:21:18 +00:00
->label('scope', 'functions.read')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2025-02-03 09:32:01 +00:00
->label('sdk', new Method(
namespace: 'functions',
name: 'getDeploymentDownload',
description: '/docs/references/functions/get-deployment-download.md',
auth: [AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::ANY,
type: MethodType::LOCATION
))
2023-08-21 10:21:18 +00:00
->param('functionId', '', new UID(), 'Function ID.')
->param('deploymentId', '', new UID(), 'Deployment ID.')
->inject('response')
->inject('request')
->inject('dbForProject')
2024-02-20 14:10:51 +00:00
->inject('deviceForFunctions')
->action(function (string $functionId, string $deploymentId, Response $response, Request $request, Database $dbForProject, Device $deviceForFunctions) {
2023-08-21 10:21:18 +00: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-20 14:10:51 +00:00
if (!$deviceForFunctions->exists($path)) {
2023-08-21 10:21:18 +00:00
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$response
->setContentType('application/gzip')
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
2023-08-21 10:21:18 +00:00
->addHeader('X-Peak', \memory_get_peak_usage())
2023-10-27 14:08:33 +00:00
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"');
2023-08-21 10:21:18 +00:00
2024-02-20 14:10:51 +00:00
$size = $deviceForFunctions->getFileSize($path);
2023-08-21 10:21:18 +00: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-20 14:10:51 +00:00
$response->send($deviceForFunctions->read($path, $start, ($end - $start + 1)));
2023-08-21 10:21:18 +00:00
}
if ($size > APP_STORAGE_READ_BUFFER) {
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
$response->chunk(
2024-02-20 14:10:51 +00:00
$deviceForFunctions->read(
2023-08-21 10:21:18 +00: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-20 14:10:51 +00:00
$response->send($deviceForFunctions->read($path));
2023-08-21 10:21:18 +00:00
}
});
App::patch('/v1/functions/:functionId/deployments/:deploymentId')
2020-07-12 21:18:52 +00:00
->groups(['api', 'functions'])
2024-07-01 19:35:55 +00:00
->desc('Update deployment')
2020-05-06 17:35:56 +00:00
->label('scope', 'functions.write')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
->label('event', 'functions.[functionId].deployments.[deploymentId].update')
2022-09-05 08:00:08 +00:00
->label('audits.event', 'deployment.update')
2022-08-13 07:34:03 +00:00
->label('audits.resource', 'function/{request.functionId}')
2021-04-16 07:22:17 +00:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2020-05-06 17:35:56 +00:00
->label('sdk.namespace', 'functions')
->label('sdk.method', 'updateDeployment')
->label('sdk.description', '/docs/references/functions/update-function-deployment.md')
2020-11-11 21:02:24 +00: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-26 15:20:08 +00:00
->inject('response')
->inject('dbForProject')
2022-12-20 16:11:30 +00:00
->inject('queueForEvents')
2025-02-03 09:32:01 +00:00
->inject('dbForPlatform')
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, Database $dbForPlatform) {
2021-01-16 23:38:13 +00:00
2022-01-06 09:45:56 +00:00
$function = $dbForProject->getDocument('functions', $functionId);
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
2022-01-26 23:19:02 +00:00
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
2021-01-16 23:38:13 +00:00
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
if ($build->isEmpty()) {
throw new Exception(Exception::BUILD_NOT_FOUND);
2020-05-06 17:35:56 +00: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 10:29:04 +00:00
'deploymentInternalId' => $deployment->getInternalId(),
'deployment' => $deployment->getId(),
])));
2023-07-28 07:56:07 +00:00
// Inform scheduler if function is still active
2025-02-03 09:32:01 +00:00
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
2023-06-11 10:29:04 +00:00
$schedule
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $function->getAttribute('schedule'))
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
2025-02-03 09:32:01 +00:00
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
2022-11-06 21:41:33 +00:00
2022-12-20 16:11:30 +00:00
$queueForEvents
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId());
$response->dynamic($function, Response::MODEL_FUNCTION);
2020-12-26 15:20:08 +00:00
});
2020-07-12 21:18:52 +00:00
App::delete('/v1/functions/:functionId')
->groups(['api', 'functions'])
2023-10-02 14:02:48 +00:00
->desc('Delete function')
2020-05-05 17:30:12 +00:00
->label('scope', 'functions.write')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
->label('event', 'functions.[functionId].delete')
2022-09-05 08:00:08 +00:00
->label('audits.event', 'function.delete')
2022-08-09 14:38:54 +00:00
->label('audits.resource', 'function/{request.functionId}')
2025-02-03 09:32:01 +00:00
->label('sdk', new Method(
namespace: 'functions',
name: 'delete',
description: '/docs/references/functions/delete-function.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::NONE
))
->param('functionId', '', new UID(), 'Function ID.')
2020-12-26 15:20:08 +00:00
->inject('response')
->inject('dbForProject')
2022-12-20 16:11:30 +00:00
->inject('queueForDeletes')
->inject('queueForEvents')
2025-02-03 09:32:01 +00:00
->inject('dbForPlatform')
->action(function (string $functionId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Database $dbForPlatform) {
2020-10-30 19:53:27 +00:00
$function = $dbForProject->getDocument('functions', $functionId);
2020-05-05 17:30:12 +00:00
2021-06-20 13:59:36 +00:00
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-07-12 21:18:52 +00:00
}
2020-05-06 12:12:52 +00:00
if (!$dbForProject->deleteDocument('functions', $function->getId())) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove function from DB');
2020-05-04 14:35:01 +00:00
}
2020-05-05 17:30:12 +00:00
2023-07-28 07:56:07 +00:00
// Inform scheduler to no longer run function
2025-02-03 09:32:01 +00:00
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
2022-11-15 16:14:52 +00:00
$schedule
2022-11-06 21:41:33 +00:00
->setAttribute('resourceUpdatedAt', DateTime::now())
2023-06-11 10:29:04 +00:00
->setAttribute('active', false);
2025-02-03 09:32:01 +00:00
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
2022-11-04 05:12:08 +00:00
2022-12-20 16:11:30 +00:00
$queueForDeletes
2022-04-17 20:34:32 +00:00
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($function);
2022-12-20 16:11:30 +00:00
$queueForEvents->setParam('functionId', $function->getId());
2020-10-30 19:53:27 +00:00
2020-07-12 21:18:52 +00:00
$response->noContent();
2020-12-26 15:20:08 +00:00
});
2020-07-12 21:18:52 +00:00
2022-01-24 23:09:24 +00:00
App::get('/v1/functions/:functionId/deployments')
2020-07-12 21:18:52 +00:00
->groups(['api', 'functions'])
2023-10-02 14:02:48 +00:00
->desc('List deployments')
2020-05-05 17:30:12 +00:00
->label('scope', 'functions.read')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2025-02-03 11:38:27 +00:00
->label('sdk', new Method(
namespace: 'functions',
name: 'listDeployments',
description: '/docs/references/functions/list-deployments.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_DEPLOYMENT_LIST,
)
]
))
->param('functionId', '', new UID(), 'Function ID.')
2023-03-29 19:38:39 +00: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-10 14:40:14 +00:00
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
2020-12-26 15:20:08 +00:00
->inject('response')
->inject('dbForProject')
2022-08-23 09:12:48 +00:00
->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject) {
2021-01-16 23:38:13 +00:00
$function = $dbForProject->getDocument('functions', $functionId);
2020-05-05 17:30:12 +00:00
2021-06-20 13:59:36 +00:00
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-07-12 21:18:52 +00:00
}
2021-05-27 10:09:14 +00:00
2024-02-12 16:02:04 +00:00
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
2021-08-06 12:36:05 +00:00
2022-08-11 23:53:52 +00:00
if (!empty($search)) {
2022-08-23 09:12:48 +00:00
$queries[] = Query::search('search', $search);
2021-08-06 12:36:05 +00:00
}
2022-08-23 09:12:48 +00:00
// Set resource queries
2024-04-29 10:24:22 +00:00
$queries[] = Query::equal('resourceInternalId', [$function->getInternalId()]);
2022-08-23 09:12:48 +00:00
$queries[] = Query::equal('resourceType', ['functions']);
2021-08-18 13:42:03 +00:00
2024-02-12 09:55:45 +00:00
/**
2024-02-12 10:03:31 +00:00
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
2024-02-12 09:55:45 +00:00
*/
2023-08-22 03:25:55 +00:00
$cursor = \array_filter($queries, function ($query) {
2023-12-06 14:10:40 +00:00
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
2023-08-22 03:25:55 +00:00
});
$cursor = reset($cursor);
2022-08-30 11:55:23 +00:00
if ($cursor) {
2022-08-23 09:12:48 +00:00
/** @var Query $cursor */
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
2022-08-23 09:12:48 +00:00
$deploymentId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('deployments', $deploymentId);
2021-08-18 13:42:03 +00:00
2022-08-11 23:53:52 +00:00
if ($cursorDocument->isEmpty()) {
2022-08-23 09:12:48 +00:00
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Deployment '{$deploymentId}' for the 'cursor' value not found.");
2022-08-11 23:53:52 +00:00
}
2021-08-18 13:42:03 +00:00
2022-08-23 09:12:48 +00:00
$cursor->setValue($cursorDocument);
2021-08-18 13:42:03 +00:00
}
2022-08-23 09:12:48 +00:00
$filterQueries = Query::groupByType($queries)['filters'];
2021-08-18 13:42:03 +00:00
2022-08-23 09:12:48 +00:00
$results = $dbForProject->find('deployments', $queries);
2022-08-11 23:53:52 +00:00
$total = $dbForProject->count('deployments', $filterQueries, APP_LIMIT_COUNT);
2020-07-12 21:18:52 +00:00
2022-01-31 11:29:31 +00:00
foreach ($results as $result) {
2022-02-16 11:43:21 +00:00
$build = $dbForProject->getDocument('builds', $result->getAttribute('buildId', ''));
$result->setAttribute('status', $build->getAttribute('status', 'processing'));
2023-08-05 14:50:28 +00:00
$result->setAttribute('buildLogs', $build->getAttribute('logs', ''));
2022-10-10 15:24:40 +00:00
$result->setAttribute('buildTime', $build->getAttribute('duration', 0));
2024-08-19 11:06:58 +00:00
$result->setAttribute('buildSize', $build->getAttribute('size', 0));
$result->setAttribute('size', $result->getAttribute('size', 0));
}
2020-10-30 19:53:27 +00:00
$response->dynamic(new Document([
2022-01-24 23:09:24 +00:00
'deployments' => $results,
2022-02-27 09:57:09 +00:00
'total' => $total,
2022-01-24 23:09:24 +00:00
]), Response::MODEL_DEPLOYMENT_LIST);
2020-12-26 15:20:08 +00:00
});
2020-07-12 21:18:52 +00:00
2022-01-24 23:11:33 +00:00
App::get('/v1/functions/:functionId/deployments/:deploymentId')
2020-07-12 21:18:52 +00:00
->groups(['api', 'functions'])
2023-10-02 14:02:48 +00:00
->desc('Get deployment')
2020-05-05 17:30:12 +00:00
->label('scope', 'functions.read')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2025-02-03 11:38:27 +00:00
->label('sdk', new Method(
namespace: 'functions',
name: 'getDeployment',
description: '/docs/references/functions/get-deployment.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_DEPLOYMENT,
)
]
))
->param('functionId', '', new UID(), 'Function ID.')
2022-01-24 23:11:33 +00:00
->param('deploymentId', '', new UID(), 'Deployment ID.')
2020-12-26 15:20:08 +00:00
->inject('response')
->inject('dbForProject')
2022-05-24 14:28:27 +00:00
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject) {
2021-01-16 23:38:13 +00:00
$function = $dbForProject->getDocument('functions', $functionId);
2020-05-05 17:30:12 +00:00
2021-06-20 13:59:36 +00:00
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-07-12 21:18:52 +00:00
}
2020-05-05 17:30:12 +00:00
2022-01-24 23:11:33 +00:00
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
2020-05-05 17:30:12 +00:00
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
2020-07-12 21:18:52 +00:00
}
2020-05-05 17:30:12 +00:00
2022-01-24 23:11:33 +00:00
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
2020-05-05 17:30:12 +00:00
}
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
$deployment->setAttribute('status', $build->getAttribute('status', 'waiting'));
2023-08-05 14:50:28 +00:00
$deployment->setAttribute('buildLogs', $build->getAttribute('logs', ''));
$deployment->setAttribute('buildTime', $build->getAttribute('duration', 0));
2024-08-19 11:06:58 +00:00
$deployment->setAttribute('buildSize', $build->getAttribute('size', 0));
$deployment->setAttribute('size', $deployment->getAttribute('size', 0));
2022-01-24 23:11:33 +00:00
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
2020-12-26 15:20:08 +00:00
});
2020-07-12 21:18:52 +00:00
2022-01-24 23:14:21 +00:00
App::delete('/v1/functions/:functionId/deployments/:deploymentId')
2020-07-12 21:18:52 +00:00
->groups(['api', 'functions'])
2023-10-02 14:02:48 +00:00
->desc('Delete deployment')
2020-05-05 17:30:12 +00:00
->label('scope', 'functions.write')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
->label('event', 'functions.[functionId].deployments.[deploymentId].delete')
2022-09-05 08:00:08 +00:00
->label('audits.event', 'deployment.delete')
2022-08-13 07:34:03 +00:00
->label('audits.resource', 'function/{request.functionId}')
2025-02-03 11:38:27 +00:00
->label('sdk', new Method(
namespace: 'functions',
name: 'deleteDeployment',
description: '/docs/references/functions/delete-deployment.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::NONE
))
->param('functionId', '', new UID(), 'Function ID.')
2022-01-24 23:14:21 +00:00
->param('deploymentId', '', new UID(), 'Deployment ID.')
2020-12-26 15:20:08 +00:00
->inject('response')
->inject('dbForProject')
2022-12-20 16:11:30 +00:00
->inject('queueForDeletes')
->inject('queueForEvents')
2024-02-20 14:10:51 +00:00
->inject('deviceForFunctions')
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Device $deviceForFunctions) {
2021-01-16 23:38:13 +00:00
$function = $dbForProject->getDocument('functions', $functionId);
2021-06-20 13:59:36 +00:00
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-07-12 21:18:52 +00:00
}
2022-01-24 23:14:21 +00:00
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
2022-01-31 23:44:55 +00:00
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
2023-06-14 08:57:30 +00: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-20 14:10:51 +00:00
if (!($deviceForFunctions->delete($deployment->getAttribute('path', '')))) {
2023-06-14 08:57:30 +00:00
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove deployment from storage');
2020-07-18 21:48:28 +00:00
}
2020-05-05 17:30:12 +00:00
}
if ($function->getAttribute('deployment') === $deployment->getId()) { // Reset function deployment
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
2022-01-24 23:14:21 +00:00
'deployment' => '',
2023-06-11 14:08:48 +00:00
'deploymentInternalId' => '',
2021-05-04 21:25:17 +00:00
])));
2020-10-30 19:53:27 +00:00
}
2022-12-20 16:11:30 +00:00
$queueForEvents
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId());
2020-07-18 21:48:28 +00:00
2022-12-20 16:11:30 +00:00
$queueForDeletes
2022-04-17 20:34:32 +00:00
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($deployment);
2020-07-12 21:18:52 +00:00
$response->noContent();
2020-12-26 15:20:08 +00:00
});
2020-07-12 21:18:52 +00:00
2024-05-29 12:18:52 +00:00
App::post('/v1/functions/:functionId/deployments/:deploymentId/build')
->alias('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
2022-09-29 08:42:46 +00:00
->groups(['api', 'functions'])
->desc('Rebuild deployment')
2022-09-29 08:42:46 +00:00
->label('scope', 'functions.write')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2022-09-29 08:42:46 +00:00
->label('event', 'functions.[functionId].deployments.[deploymentId].update')
->label('audits.event', 'deployment.update')
->label('audits.resource', 'function/{request.functionId}')
2025-02-03 11:38:27 +00:00
->label('sdk', new Method(
namespace: 'functions',
name: 'createBuild',
description: '/docs/references/functions/create-build.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE,
)
]
))
2022-09-29 08:42:46 +00:00
->param('functionId', '', new UID(), 'Function ID.')
->param('deploymentId', '', new UID(), 'Deployment ID.')
->param('buildId', '', new UID(), 'Build unique ID.', true) // added as optional param for backward compatibility
->inject('request')
2022-09-29 08:42:46 +00:00
->inject('response')
->inject('dbForProject')
->inject('project')
2022-12-20 16:11:30 +00:00
->inject('queueForEvents')
->inject('queueForBuilds')
2024-07-25 08:36:11 +00:00
->inject('deviceForFunctions')
->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Device $deviceForFunctions) {
2022-09-29 08:42:46 +00:00
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
2023-08-19 06:15:47 +00:00
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
2022-09-29 08:42:46 +00:00
if ($deployment->isEmpty()) {
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
2024-07-25 08:36:11 +00:00
$path = $deployment->getAttribute('path');
2024-09-05 02:25:11 +00:00
if (empty($path) || !$deviceForFunctions->exists($path)) {
2024-07-25 08:36:11 +00:00
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
}
$deploymentId = ID::unique();
2022-09-29 08:42:46 +00:00
2024-07-25 08:36:11 +00:00
$destination = $deviceForFunctions->getPath($deploymentId . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
$deviceForFunctions->transfer($path, $destination, $deviceForFunctions);
2023-09-13 19:18:50 +00:00
$deployment->removeAttribute('$internalId');
2023-08-19 06:15:47 +00:00
$deployment = $dbForProject->createDocument('deployments', $deployment->setAttributes([
2024-07-25 08:36:11 +00:00
'$internalId' => '',
'$id' => $deploymentId,
2023-08-19 06:15:47 +00:00
'buildId' => '',
'buildInternalId' => '',
2024-07-25 08:36:11 +00:00
'path' => $destination,
2023-08-19 06:15:47 +00:00
'entrypoint' => $function->getAttribute('entrypoint'),
'commands' => $function->getAttribute('commands', ''),
'search' => implode(' ', [$deploymentId, $function->getAttribute('entrypoint')]),
2023-08-19 06:15:47 +00:00
]));
2022-09-29 08:42:46 +00:00
2022-12-20 16:11:30 +00:00
$queueForBuilds
2023-08-19 06:15:47 +00:00
->setType(BUILD_TYPE_DEPLOYMENT)
2022-09-29 08:42:46 +00:00
->setResource($function)
2024-02-20 11:40:55 +00:00
->setDeployment($deployment);
2022-09-29 08:42:46 +00:00
$queueForEvents
2023-08-17 21:37:52 +00:00
->setParam('functionId', $function->getId())
->setParam('deploymentId', $deployment->getId());
2022-09-29 08:42:46 +00:00
$response->noContent();
});
App::patch('/v1/functions/:functionId/deployments/:deploymentId/build')
2024-02-18 12:31:25 +00:00
->groups(['api', 'functions'])
2024-05-30 20:23:29 +00:00
->desc('Cancel deployment')
2024-02-18 12:31:25 +00:00
->label('scope', 'functions.write')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2024-02-18 12:31:25 +00:00
->label('audits.event', 'deployment.update')
->label('audits.resource', 'function/{request.functionId}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'functions')
2024-05-30 20:23:29 +00:00
->label('sdk.method', 'updateDeploymentBuild')
2024-02-18 17:55:57 +00:00
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_BUILD)
2024-02-18 12:31:25 +00:00
->param('functionId', '', new UID(), 'Function ID.')
->param('deploymentId', '', new UID(), 'Deployment ID.')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('queueForEvents')
2024-03-04 20:30:06 +00:00
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents) {
2024-02-18 12:31:25 +00: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);
}
2024-03-04 20:30:06 +00:00
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
2024-02-18 12:31:25 +00:00
if ($build->isEmpty()) {
2024-05-30 20:23:29 +00:00
$buildId = ID::unique();
$build = $dbForProject->createDocument('builds', new Document([
'$id' => $buildId,
'$permissions' => [],
'startTime' => DateTime::now(),
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentId' => $deployment->getId(),
2024-06-12 10:21:48 +00:00
'status' => 'canceled',
2024-05-30 20:23:29 +00:00
'path' => '',
'runtime' => $function->getAttribute('runtime'),
'source' => $deployment->getAttribute('path', ''),
'sourceType' => '',
'logs' => '',
'duration' => 0,
'size' => 0
]));
2024-02-18 12:31:25 +00:00
2024-05-30 20:23:29 +00:00
$deployment->setAttribute('buildId', $build->getId());
$deployment->setAttribute('buildInternalId', $build->getInternalId());
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
} else {
if (\in_array($build->getAttribute('status'), ['ready', 'failed'])) {
throw new Exception(Exception::BUILD_ALREADY_COMPLETED);
}
2024-06-11 14:50:50 +00:00
2024-06-11 14:48:55 +00:00
$startTime = new \DateTime($build->getAttribute('startTime'));
$endTime = new \DateTime('now');
$duration = $endTime->getTimestamp() - $startTime->getTimestamp();
2024-06-07 11:23:33 +00:00
$build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttributes([
'endTime' => DateTime::now(),
'duration' => $duration,
2024-06-12 10:21:48 +00:00
'status' => 'canceled'
2024-06-07 11:23:33 +00:00
]));
}
2024-08-22 10:51:08 +00:00
$dbForProject->purgeCachedDocument('deployments', $deployment->getId());
try {
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
$executor->deleteRuntime($project->getId(), $deploymentId . "-build");
} catch (\Throwable $th) {
// Don't throw if the deployment doesn't exist
if ($th->getCode() !== 404) {
throw $th;
}
}
2024-02-18 12:31:25 +00:00
$queueForEvents
->setParam('functionId', $function->getId())
2023-08-17 21:37:52 +00:00
->setParam('deploymentId', $deployment->getId());
2024-02-18 17:55:57 +00:00
$response->dynamic($build, Response::MODEL_BUILD);
2022-09-29 08:42:46 +00:00
});
2020-07-12 21:18:52 +00:00
App::post('/v1/functions/:functionId/executions')
->groups(['api', 'functions'])
2023-10-02 14:02:48 +00:00
->desc('Create execution')
2020-12-30 07:26:01 +00:00
->label('scope', 'execution.write')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
->label('event', 'functions.[functionId].executions.[executionId].create')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2025-02-03 09:32:01 +00:00
->label('sdk', new Method(
namespace: 'functions',
name: 'createExecution',
description: '/docs/references/functions/create-execution.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_EXECUTION,
)
],
contentType: ContentType::MULTIPART,
requestType: 'application/json',
))
->param('functionId', '', new UID(), 'Function ID.')
2024-10-08 01:17:34 +00:00
->param('body', '', new Text(10485760, 0), 'HTTP body of execution. Default value is empty string.', true)
->param('async', false, new Boolean(true), 'Execute code in the background. Default value is false.', true)
2023-09-06 08:16:01 +00:00
->param('path', '/', new Text(2048), 'HTTP path of execution. Path can include query params. Default value is /', true)
2023-07-01 06:46:21 +00:00
->param('method', 'POST', new Whitelist(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], true), 'HTTP method of execution. Default value is GET.', true)
2024-08-15 12:12:15 +00:00
->param('headers', [], new AnyOf([new Assoc(), new Text(65535)], AnyOf::TYPE_MIXED), 'HTTP headers of execution. Defaults to empty.', true)
2024-11-04 08:50:22 +00:00
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true, precision: DateTimeValidator::PRECISION_MINUTES, offset: 60), 'Scheduled execution time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.', true)
2020-12-26 15:20:08 +00:00
->inject('response')
2024-08-05 13:08:41 +00:00
->inject('request')
2020-12-26 15:20:08 +00:00
->inject('project')
->inject('dbForProject')
2025-02-03 09:32:01 +00:00
->inject('dbForPlatform')
->inject('user')
2022-12-20 16:11:30 +00:00
->inject('queueForEvents')
2023-10-25 07:39:59 +00:00
->inject('queueForUsage')
2022-11-16 10:33:11 +00:00
->inject('queueForFunctions')
->inject('geodb')
2025-02-03 09:32:01 +00:00
->action(function (string $functionId, string $body, mixed $async, string $path, string $method, mixed $headers, ?string $scheduledAt, Response $response, Request $request, Document $project, Database $dbForProject, Database $dbForPlatform, Document $user, Event $queueForEvents, Usage $queueForUsage, Func $queueForFunctions, Reader $geodb) {
2024-09-25 08:59:51 +00:00
$async = \strval($async) === 'true' || \strval($async) === '1';
2024-06-07 19:05:29 +00:00
2024-09-05 02:25:11 +00:00
if (!$async && !is_null($scheduledAt)) {
2024-06-13 08:24:51 +00:00
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Scheduled executions must run asynchronously. Set scheduledAt to a future date, or set async to true.');
2024-06-07 19:05:29 +00:00
}
2020-07-16 21:50:37 +00:00
2024-08-05 13:08:41 +00:00
/**
* @var array<string, mixed> $headers
*/
$assocParams = ['headers'];
foreach ($assocParams as $assocParam) {
if (!empty('headers') && !is_array($$assocParam)) {
$$assocParam = \json_decode($$assocParam, true);
}
}
2024-08-06 17:31:09 +00:00
$booleanParams = ['async'];
foreach ($booleanParams as $booleamParam) {
if (!empty($$booleamParam) && !is_bool($$booleamParam)) {
$$booleamParam = $$booleamParam === "true" ? true : false;
}
}
2024-08-05 13:08:41 +00:00
// 'headers' validator
2024-08-06 08:19:28 +00:00
$validator = new Headers();
2024-08-05 13:08:41 +00:00
if (!$validator->isValid($headers)) {
throw new Exception($validator->getDescription(), 400);
}
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
2020-07-12 21:18:52 +00:00
2023-08-16 21:58:25 +00:00
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
2023-08-16 21:58:25 +00:00
if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-07-12 21:18:52 +00:00
}
2020-07-16 21:50:37 +00:00
2023-09-04 17:53:25 +00:00
$version = $function->getAttribute('version', 'v2');
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
$spec = Config::getParam('runtime-specifications')[$function->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
2022-02-05 19:49:57 +00:00
$runtime = (isset($runtimes[$function->getAttribute('runtime', '')])) ? $runtimes[$function->getAttribute('runtime', '')] : null;
2022-02-04 01:29:40 +00:00
if (\is_null($runtime)) {
throw new Exception(Exception::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
2022-02-04 01:29:40 +00:00
}
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', '')));
2020-07-16 21:50:37 +00:00
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
2022-08-14 06:56:12 +00:00
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function');
2020-07-16 21:50:37 +00:00
}
2022-01-24 23:16:53 +00:00
if ($deployment->isEmpty()) {
2022-08-14 06:56:12 +00:00
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function');
2020-07-16 21:50:37 +00:00
}
2020-12-29 23:00:44 +00: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-29 23:00:44 +00:00
2021-05-04 21:25:17 +00:00
if (!$validator->isValid($function->getAttribute('execute'))) { // Check if user has write access to execute function
2022-07-26 14:56:59 +00:00
throw new Exception(Exception::USER_UNAUTHORIZED, $validator->getDescription());
2020-12-29 23:00:44 +00:00
}
2024-01-29 11:21:51 +00:00
$jwt = ''; // initialize
2021-06-20 13:59:36 +00:00
if (!$user->isEmpty()) { // If userId exists, generate a JWT for function
2021-07-22 14:49:52 +00:00
$sessions = $user->getAttribute('sessions', []);
$current = new Document();
foreach ($sessions as $session) {
/** @var Utopia\Database\Document $session */
2021-07-22 14:49:52 +00:00
if ($session->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too
$current = $session;
}
}
if (!$current->isEmpty()) {
2024-05-28 09:25:54 +00:00
$jwtExpiry = $function->getAttribute('timeout', 900);
2024-05-29 07:51:51 +00:00
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0);
2024-01-29 11:21:51 +00:00
$jwt = $jwtObj->encode([
2021-03-10 17:48:05 +00:00
'userId' => $user->getId(),
2021-07-22 14:49:52 +00:00
'sessionId' => $current->getId(),
2021-03-10 17:48:05 +00:00
]);
}
}
2024-01-29 11:15:07 +00:00
$jwtExpiry = $function->getAttribute('timeout', 900);
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
]);
2024-05-14 11:58:31 +00:00
$headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $apiKey;
$headers['x-appwrite-trigger'] = 'http';
$headers['x-appwrite-user-id'] = $user->getId() ?? '';
2024-01-29 11:21:51 +00:00
$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 07:01:51 +00: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-11 13:34:57 +00:00
if (\in_array(\strtolower($key), FUNCTION_ALLOWLIST_HEADERS_REQUEST)) {
2023-08-09 15:53:58 +00:00
$headersFiltered[] = ['name' => $key, 'value' => $value];
}
}
2023-07-29 16:20:20 +00:00
$executionId = ID::unique();
2024-06-07 19:05:29 +00:00
$status = $async ? 'waiting' : 'processing';
2024-09-05 02:25:11 +00:00
if (!is_null($scheduledAt)) {
2024-06-07 19:05:29 +00:00
$status = 'scheduled';
}
2023-07-29 16:20:20 +00:00
$execution = new Document([
'$id' => $executionId,
'$permissions' => !$user->isEmpty() ? [Permission::read(Role::user($user->getId()))] : [],
2024-11-29 12:57:51 +00:00
'resourceInternalId' => $function->getInternalId(),
'resourceId' => $function->getId(),
'resourceType' => 'functions',
2023-07-29 16:20:20 +00:00
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentId' => $deployment->getId(),
2024-06-17 12:44:12 +00:00
'trigger' => (!is_null($scheduledAt)) ? 'schedule' : 'http',
'status' => $status, // waiting / processing / completed / failed / scheduled
2023-07-29 16:20:20 +00:00
'responseStatusCode' => 0,
'responseHeaders' => [],
'requestPath' => $path,
'requestMethod' => $method,
'requestHeaders' => $headersFiltered,
2023-07-29 16:20:20 +00:00
'errors' => '',
'logs' => '',
'duration' => 0.0,
'search' => implode(' ', [$functionId, $executionId]),
]);
2022-12-20 16:11:30 +00: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 10:51:49 +00:00
->setContext('function', $function);
2021-08-24 09:32:27 +00:00
if ($async) {
2024-09-05 02:25:11 +00:00
if (is_null($scheduledAt)) {
2024-07-22 10:36:18 +00:00
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
2024-06-07 19:05:29 +00:00
$queueForFunctions
->setType('http')
->setExecution($execution)
->setFunction($function)
->setBody($body)
->setHeaders($headers)
->setPath($path)
->setMethod($method)
->setJWT($jwt)
->setProject($project)
->setUser($user)
->setParam('functionId', $function->getId())
->setParam('executionId', $execution->getId())
->trigger();
} else {
2024-06-28 21:42:55 +00:00
$data = [
2024-06-26 11:30:23 +00:00
'headers' => $headers,
'path' => $path,
'method' => $method,
'body' => $body,
2024-09-07 10:20:23 +00:00
'userId' => $user->getId()
2024-06-26 11:30:23 +00:00
];
2025-02-03 09:32:01 +00:00
$schedule = $dbForPlatform->createDocument('schedules', new Document([
2024-06-07 19:05:29 +00:00
'region' => System::getEnv('_APP_REGION', 'default'),
'resourceType' => ScheduleExecutions::getSupportedResource(),
2024-06-17 12:44:12 +00:00
'resourceId' => $execution->getId(),
'resourceInternalId' => $execution->getInternalId(),
2024-06-07 19:05:29 +00:00
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $scheduledAt,
2024-06-28 21:42:55 +00:00
'data' => $data,
2024-06-07 19:05:29 +00:00
'active' => true,
]));
2024-07-22 10:36:18 +00:00
$execution = $execution
->setAttribute('scheduleId', $schedule->getId())
->setAttribute('scheduleInternalId', $schedule->getInternalId())
->setAttribute('scheduledAt', $scheduledAt);
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
2024-06-07 19:05:29 +00:00
}
2020-07-12 21:18:52 +00:00
return $response
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
->dynamic($execution, Response::MODEL_EXECUTION);
2021-08-24 09:32:27 +00:00
}
2022-01-10 14:18:33 +00:00
2023-03-28 13:21:42 +00:00
$durationStart = \microtime(true);
2022-08-10 13:43:05 +00:00
2023-03-11 16:06:02 +00:00
$vars = [];
2023-09-11 10:22:16 +00: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-11 16:06:02 +00:00
// Shared vars
2023-09-05 08:21:36 +00:00
foreach ($function->getAttribute('varsProject', []) as $var) {
2023-08-18 06:55:44 +00:00
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
2022-08-10 13:43:05 +00:00
2023-03-11 16:06:02 +00:00
// Function vars
2023-09-05 08:21:36 +00:00
foreach ($function->getAttribute('vars', []) as $var) {
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
2023-03-11 16:06:02 +00:00
2024-05-06 11:27:28 +00:00
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$hostname = System::getEnv('_APP_DOMAIN');
2024-01-29 11:15:07 +00:00
$endpoint = $protocol . '://' . $hostname . "/v1";
2023-03-11 16:06:02 +00:00
// Appwrite vars
2022-08-10 13:43:05 +00:00
$vars = \array_merge($vars, [
2024-05-09 11:50:45 +00:00
'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint,
'APPWRITE_FUNCTION_ID' => $functionId,
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'),
'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(),
'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(),
2022-09-19 11:58:41 +00:00
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '',
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '',
'APPWRITE_COMPUTE_CPUS' => $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
'APPWRITE_COMPUTE_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', ''),
2022-02-04 01:29:40 +00:00
]);
2022-02-05 19:49:57 +00:00
/** Execute function */
2024-04-01 11:02:47 +00:00
$executor = new Executor(System::getEnv('_APP_EXECUTOR_HOST'));
try {
2023-09-04 17:53:25 +00:00
$version = $function->getAttribute('version', 'v2');
$command = $runtime['startCommand'];
2023-09-04 17:53:25 +00: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 08:49:45 +00:00
variables: $vars,
timeout: $function->getAttribute('timeout', 0),
2022-11-08 08:49:45 +00:00
image: $runtime['image'],
2023-03-15 06:08:43 +00:00
source: $build->getAttribute('path', ''),
entrypoint: $deployment->getAttribute('entrypoint', ''),
2023-09-04 17:53:25 +00:00
version: $version,
2023-02-14 11:01:38 +00:00
path: $path,
method: $method,
headers: $headers,
2023-10-15 17:41:09 +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-10-15 17:41:09 +00:00
requestTimeout: 30
);
$headersFiltered = [];
foreach ($executionResponse['headers'] as $key => $value) {
2023-08-11 13:34:57 +00:00
if (\in_array(\strtolower($key), FUNCTION_ALLOWLIST_HEADERS_RESPONSE)) {
2023-08-09 15:53:58 +00:00
$headersFiltered[] = ['name' => $key, 'value' => $value];
}
}
2023-07-29 16:20:20 +00:00
/** Update execution status */
2024-08-08 08:38:15 +00:00
$status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed';
2023-02-02 19:21:00 +00:00
$execution->setAttribute('status', $status);
2023-07-29 16:20:20 +00:00
$execution->setAttribute('responseStatusCode', $executionResponse['statusCode']);
$execution->setAttribute('responseHeaders', $headersFiltered);
2023-02-14 11:01:38 +00:00
$execution->setAttribute('logs', $executionResponse['logs']);
$execution->setAttribute('errors', $executionResponse['errors']);
$execution->setAttribute('duration', $executionResponse['duration']);
} catch (\Throwable $th) {
2023-03-28 13:21:42 +00:00
$durationEnd = \microtime(true);
2022-07-02 14:25:44 +00:00
$execution
2023-03-28 13:21:42 +00:00
->setAttribute('duration', $durationEnd - $durationStart)
2022-07-02 14:25:44 +00:00
->setAttribute('status', 'failed')
2023-07-29 16:20:20 +00:00
->setAttribute('responseStatusCode', 500)
2023-02-15 08:36:20 +00:00
->setAttribute('errors', $th->getMessage() . '\nError Code: ' . $th->getCode());
Console::error($th->getMessage());
2024-05-20 10:44:08 +00:00
if ($th instanceof AppwriteException) {
throw $th;
}
2023-12-14 04:49:16 +00:00
} finally {
2023-12-12 19:21:47 +00:00
$queueForUsage
->addMetric(METRIC_EXECUTIONS, 1)
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1)
2023-12-14 04:49:16 +00: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
->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT)))
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT) * $execution->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT)))
2023-12-12 19:21:47 +00:00
;
2022-02-04 01:29:40 +00:00
2024-07-02 11:38:59 +00:00
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
2023-10-05 13:15:39 +00:00
}
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
2022-08-16 05:23:51 +00:00
if (!$isPrivilegedUser && !$isAppUser) {
2023-02-14 11:01:38 +00:00
$execution->setAttribute('logs', '');
$execution->setAttribute('errors', '');
2022-08-16 04:34:49 +00:00
}
2022-07-13 08:57:57 +00:00
2023-08-06 13:11:30 +00:00
$headers = [];
2023-08-16 06:19:42 +00:00
foreach (($executionResponse['headers'] ?? []) as $key => $value) {
2023-08-09 15:53:58 +00:00
$headers[] = ['name' => $key, 'value' => $value];
2023-08-06 13:11:30 +00:00
}
2023-08-16 06:19:42 +00:00
$execution->setAttribute('responseBody', $executionResponse['body'] ?? '');
2023-08-06 13:11:30 +00:00
$execution->setAttribute('responseHeaders', $headers);
2023-02-27 10:20:37 +00:00
2024-08-11 17:36:05 +00:00
$acceptTypes = \explode(', ', $request->getHeader('accept'));
2024-08-05 13:08:41 +00:00
foreach ($acceptTypes as $acceptType) {
2024-09-05 02:25:11 +00:00
if (\str_starts_with($acceptType, 'application/json') || \str_starts_with($acceptType, 'application/*')) {
2024-08-11 17:36:05 +00:00
$response->setContentType(Response::CONTENT_TYPE_JSON);
break;
} elseif (\str_starts_with($acceptType, 'multipart/form-data') || \str_starts_with($acceptType, 'multipart/*')) {
$response->setContentType(Response::CONTENT_TYPE_MULTIPART);
2024-08-05 13:08:41 +00:00
break;
}
}
2024-08-07 13:15:53 +00:00
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($execution, Response::MODEL_EXECUTION);
2020-12-26 15:20:08 +00:00
});
2020-07-12 21:18:52 +00:00
App::get('/v1/functions/:functionId/executions')
->groups(['api', 'functions'])
2023-10-02 14:02:48 +00:00
->desc('List executions')
2020-12-30 07:26:01 +00:00
->label('scope', 'execution.read')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2025-02-03 09:32:01 +00:00
->label('sdk', new Method(
namespace: 'functions',
name: 'listExecutions',
description: '/docs/references/functions/list-executions.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_EXECUTION_LIST,
)
]
))
->param('functionId', '', new UID(), 'Function ID.')
2023-03-29 19:38:39 +00: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-26 15:20:08 +00: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-05 17:30:12 +00:00
2023-08-16 21:58:25 +00:00
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
2023-08-16 21:58:25 +00:00
if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-07-12 21:18:52 +00:00
}
2021-05-27 10:09:14 +00:00
2024-02-12 16:02:04 +00:00
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
2021-08-06 12:36:05 +00:00
2022-08-11 23:53:52 +00:00
if (!empty($search)) {
2022-08-23 09:26:34 +00:00
$queries[] = Query::search('search', $search);
2021-08-06 12:36:05 +00:00
}
2022-08-23 09:26:34 +00:00
// Set internal queries
2024-12-02 13:18:50 +00:00
$queries[] = Query::equal('resourceInternalId', [$function->getInternalId()]);
2024-11-29 12:57:51 +00:00
$queries[] = Query::equal('resourceType', ['functions']);
2024-02-12 09:55:45 +00:00
/**
2024-02-12 10:03:31 +00:00
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
2024-02-12 09:55:45 +00:00
*/
2023-08-22 03:25:55 +00:00
$cursor = \array_filter($queries, function ($query) {
2023-12-06 14:10:40 +00:00
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
2023-08-22 03:25:55 +00:00
});
$cursor = reset($cursor);
2022-08-30 11:55:23 +00:00
if ($cursor) {
2022-08-23 09:26:34 +00:00
/** @var Query $cursor */
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
2022-08-23 09:26:34 +00:00
$executionId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('executions', $executionId);
2021-08-06 12:36:05 +00:00
2022-08-11 23:53:52 +00:00
if ($cursorDocument->isEmpty()) {
2022-08-23 09:26:34 +00:00
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Execution '{$executionId}' for the 'cursor' value not found.");
2021-08-06 12:36:05 +00:00
}
2022-08-23 09:26:34 +00:00
$cursor->setValue($cursorDocument);
}
2022-08-23 09:26:34 +00:00
$filterQueries = Query::groupByType($queries)['filters'];
$results = $dbForProject->find('executions', $queries);
2022-08-11 23:53:52 +00:00
$total = $dbForProject->count('executions', $filterQueries, APP_LIMIT_COUNT);
2020-07-12 21:18:52 +00:00
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
2022-08-16 04:34:49 +00:00
if (!$isPrivilegedUser && !$isAppUser) {
$results = array_map(function ($execution) {
2023-02-14 11:01:38 +00:00
$execution->setAttribute('logs', '');
$execution->setAttribute('errors', '');
2022-08-14 09:23:41 +00:00
return $execution;
}, $results);
}
2020-07-12 21:18:52 +00:00
2020-10-30 19:53:27 +00:00
$response->dynamic(new Document([
2021-05-27 10:09:14 +00:00
'executions' => $results,
2022-02-27 09:57:09 +00:00
'total' => $total,
2020-10-30 19:53:27 +00:00
]), Response::MODEL_EXECUTION_LIST);
2020-12-26 15:20:08 +00:00
});
2020-07-12 21:18:52 +00:00
App::get('/v1/functions/:functionId/executions/:executionId')
->groups(['api', 'functions'])
2023-10-02 14:02:48 +00:00
->desc('Get execution')
2020-12-30 07:26:01 +00:00
->label('scope', 'execution.read')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2025-02-03 09:32:01 +00:00
->label('sdk', new Method(
namespace: 'functions',
name: 'getExecution',
description: '/docs/references/functions/get-execution.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_EXECUTION,
)
]
))
->param('functionId', '', new UID(), 'Function ID.')
->param('executionId', '', new UID(), 'Execution ID.')
2020-12-26 15:20:08 +00: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-05 17:30:12 +00:00
2023-08-16 21:58:25 +00:00
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
2023-08-16 21:58:25 +00:00
if ($function->isEmpty() || (!$function->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2020-07-12 21:18:52 +00:00
}
2020-05-05 17:30:12 +00:00
$execution = $dbForProject->getDocument('executions', $executionId);
2020-05-05 17:30:12 +00:00
2024-12-02 13:18:50 +00:00
if ($execution->getAttribute('resourceType') !== 'functions' && $execution->getAttribute('resourceInternalId') !== $function->getInternalId()) {
throw new Exception(Exception::EXECUTION_NOT_FOUND);
2020-07-12 21:18:52 +00:00
}
2020-05-05 17:30:12 +00:00
2021-06-20 13:59:36 +00:00
if ($execution->isEmpty()) {
throw new Exception(Exception::EXECUTION_NOT_FOUND);
2020-05-05 17:30:12 +00:00
}
2020-07-12 21:18:52 +00:00
$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
2022-08-16 05:23:51 +00:00
if (!$isPrivilegedUser && !$isAppUser) {
2023-02-14 11:01:38 +00:00
$execution->setAttribute('logs', '');
$execution->setAttribute('errors', '');
2020-05-05 17:30:12 +00:00
}
2020-07-12 21:18:52 +00:00
2020-10-30 19:53:27 +00:00
$response->dynamic($execution, Response::MODEL_EXECUTION);
2020-12-26 15:20:08 +00:00
});
2024-06-27 15:17:14 +00:00
App::delete('/v1/functions/:functionId/executions/:executionId')
->groups(['api', 'functions'])
->desc('Delete execution')
->label('scope', 'execution.write')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2024-06-27 15:17:14 +00:00
->label('event', 'functions.[functionId].executions.[executionId].delete')
->label('audits.event', 'executions.delete')
->label('audits.resource', 'function/{request.functionId}')
2025-02-03 09:32:01 +00:00
->label('sdk', new Method(
namespace: 'functions',
name: 'deleteExecution',
description: '/docs/references/functions/delete-execution.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::NONE
))
2024-06-27 15:17:14 +00:00
->param('functionId', '', new UID(), 'Function ID.')
->param('executionId', '', new UID(), 'Execution ID.')
->inject('response')
->inject('dbForProject')
2025-02-03 09:32:01 +00:00
->inject('dbForPlatform')
2024-06-27 15:17:14 +00:00
->inject('queueForEvents')
2025-02-03 09:32:01 +00:00
->action(function (string $functionId, string $executionId, Response $response, Database $dbForProject, Database $dbForPlatform, Event $queueForEvents) {
2024-06-27 15:17:14 +00:00
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$execution = $dbForProject->getDocument('executions', $executionId);
if ($execution->isEmpty()) {
throw new Exception(Exception::EXECUTION_NOT_FOUND);
}
2024-12-02 13:18:50 +00:00
if ($execution->getAttribute('resourceType') !== 'functions' && $execution->getAttribute('resourceInternalId') !== $function->getInternalId()) {
2024-06-27 15:17:14 +00:00
throw new Exception(Exception::EXECUTION_NOT_FOUND);
}
$status = $execution->getAttribute('status');
2024-06-27 15:17:14 +00:00
if (!in_array($status, ['completed', 'failed', 'scheduled'])) {
2024-06-27 15:17:14 +00:00
throw new Exception(Exception::EXECUTION_IN_PROGRESS);
}
if (!$dbForProject->deleteDocument('executions', $execution->getId())) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove execution from DB');
}
if ($status === 'scheduled') {
2025-02-03 09:32:01 +00:00
$schedule = $dbForPlatform->findOne('schedules', [
Query::equal('resourceId', [$execution->getId()]),
Query::equal('resourceType', [ScheduleExecutions::getSupportedResource()]),
Query::equal('active', [true]),
]);
if (!$schedule->isEmpty()) {
$schedule
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('active', false);
2025-02-03 09:32:01 +00:00
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
}
}
2024-06-27 15:17:14 +00:00
$queueForEvents
->setParam('functionId', $function->getId())
2024-07-03 07:02:41 +00:00
->setParam('executionId', $execution->getId())
->setPayload($response->output($execution, Response::MODEL_EXECUTION));
2024-06-27 15:17:14 +00:00
$response->noContent();
});
2022-08-01 15:13:47 +00:00
// Variables
2022-08-09 15:29:24 +00:00
App::post('/v1/functions/:functionId/variables')
2023-10-02 14:02:48 +00:00
->desc('Create variable')
2022-08-01 15:13:47 +00:00
->groups(['api', 'functions'])
->label('scope', 'functions.write')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2022-09-05 08:00:08 +00:00
->label('audits.event', 'variable.create')
2022-09-04 08:13:44 +00:00
->label('audits.resource', 'function/{request.functionId}')
2025-02-03 11:38:27 +00:00
->label('sdk', new Method(
namespace: 'functions',
name: 'createVariable',
description: '/docs/references/functions/create-variable.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_VARIABLE,
)
]
))
2022-09-19 10:05:42 +00: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-12 19:08:44 +00:00
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', false)
2024-10-24 08:59:47 +00:00
->param('secret', false, new Boolean(), 'Is secret? Secret variables can only be updated or deleted, they cannot be read.', true)
2022-08-01 15:13:47 +00:00
->inject('response')
->inject('dbForProject')
2025-02-03 09:32:01 +00:00
->inject('dbForPlatform')
->action(function (string $functionId, string $key, string $value, bool $secret, Response $response, Database $dbForProject, Database $dbForPlatform) {
2022-08-01 15:13:47 +00:00
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2022-08-01 15:13:47 +00:00
}
2022-08-26 13:38:39 +00:00
$variableId = ID::unique();
2022-08-01 15:13:47 +00:00
$variable = new Document([
2022-08-26 13:38:39 +00:00
'$id' => $variableId,
2022-08-24 15:07:18 +00:00
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
2023-03-11 16:06:02 +00:00
'resourceInternalId' => $function->getInternalId(),
'resourceId' => $function->getId(),
'resourceType' => 'function',
2022-08-01 15:13:47 +00:00
'key' => $key,
2022-08-26 13:38:39 +00:00
'value' => $value,
2024-10-21 14:33:57 +00:00
'secret' => $secret,
2023-03-11 16:06:02 +00:00
'search' => implode(' ', [$variableId, $function->getId(), $key, 'function']),
2022-08-01 15:13:47 +00:00
]);
try {
$variable = $dbForProject->createDocument('variables', $variable);
} catch (DuplicateException $th) {
throw new Exception(Exception::VARIABLE_ALREADY_EXISTS);
2022-08-01 15:13:47 +00:00
}
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false));
2022-08-01 15:13:47 +00:00
2023-07-28 07:56:07 +00:00
// Inform scheduler to pull the latest changes
2025-02-03 09:32:01 +00:00
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
2023-06-11 10:29:04 +00:00
$schedule
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $function->getAttribute('schedule'))
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
2025-02-03 09:32:01 +00:00
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
2023-06-11 10:29:04 +00:00
2022-09-07 11:11:10 +00:00
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($variable, Response::MODEL_VARIABLE);
2022-08-01 15:13:47 +00:00
});
2022-08-09 15:29:24 +00:00
App::get('/v1/functions/:functionId/variables')
2023-10-02 14:02:48 +00:00
->desc('List variables')
2022-08-01 15:13:47 +00:00
->groups(['api', 'functions'])
->label('scope', 'functions.read')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2025-02-03 11:38:27 +00:00
->label(
'sdk',
new Method(
namespace: 'functions',
name: 'listVariables',
description: '/docs/references/functions/list-variables.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_VARIABLE_LIST,
)
],
)
)
2022-09-19 10:05:42 +00:00
->param('functionId', '', new UID(), 'Function unique ID.', false)
2022-08-01 15:13:47 +00:00
->inject('response')
->inject('dbForProject')
->action(function (string $functionId, Response $response, Database $dbForProject) {
2022-08-01 15:13:47 +00:00
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2022-08-01 15:13:47 +00:00
}
$response->dynamic(new Document([
2023-09-05 08:21:36 +00:00
'variables' => $function->getAttribute('vars', []),
'total' => \count($function->getAttribute('vars', [])),
2022-08-01 15:13:47 +00:00
]), Response::MODEL_VARIABLE_LIST);
});
2022-08-09 15:29:24 +00:00
App::get('/v1/functions/:functionId/variables/:variableId')
2023-10-02 14:02:48 +00:00
->desc('Get variable')
2022-08-01 15:13:47 +00:00
->groups(['api', 'functions'])
->label('scope', 'functions.read')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2022-08-03 13:32:50 +00:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2022-08-01 15:13:47 +00:00
->label('sdk.namespace', 'functions')
->label('sdk.method', 'getVariable')
->label('sdk.description', '/docs/references/functions/get-variable.md')
2022-08-01 15:13:47 +00: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 10:05:42 +00:00
->param('functionId', '', new UID(), 'Function unique ID.', false)
->param('variableId', '', new UID(), 'Variable unique ID.', false)
2022-08-01 15:13:47 +00:00
->inject('response')
->inject('dbForProject')
2022-08-02 10:05:58 +00:00
->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject) {
2022-08-01 15:13:47 +00:00
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2022-08-01 15:13:47 +00:00
}
2023-07-24 13:12:36 +00:00
$variable = $dbForProject->getDocument('variables', $variableId);
2023-08-18 06:55:44 +00:00
if (
$variable === false ||
$variable->isEmpty() ||
$variable->getAttribute('resourceInternalId') !== $function->getInternalId() ||
$variable->getAttribute('resourceType') !== 'function'
) {
2023-07-24 13:12:36 +00:00
throw new Exception(Exception::VARIABLE_NOT_FOUND);
}
2022-08-01 15:13:47 +00:00
if ($variable === false || $variable->isEmpty()) {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
2022-08-01 15:13:47 +00:00
}
$response->dynamic($variable, Response::MODEL_VARIABLE);
});
2022-08-09 15:29:24 +00:00
App::put('/v1/functions/:functionId/variables/:variableId')
2023-10-02 14:02:48 +00:00
->desc('Update variable')
2022-08-01 15:13:47 +00:00
->groups(['api', 'functions'])
->label('scope', 'functions.write')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2022-09-05 08:00:08 +00:00
->label('audits.event', 'variable.update')
2022-09-04 08:13:44 +00:00
->label('audits.resource', 'function/{request.functionId}')
2022-08-03 13:32:50 +00:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2022-08-01 15:13:47 +00:00
->label('sdk.namespace', 'functions')
->label('sdk.method', 'updateVariable')
->label('sdk.description', '/docs/references/functions/update-variable.md')
2022-08-01 15:13:47 +00: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 10:05:42 +00: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-12 19:08:44 +00:00
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true)
2022-08-01 15:13:47 +00:00
->inject('response')
->inject('dbForProject')
2025-02-03 09:32:01 +00:00
->inject('dbForPlatform')
->action(function (string $functionId, string $variableId, string $key, ?string $value, Response $response, Database $dbForProject, Database $dbForPlatform) {
2022-08-01 15:13:47 +00:00
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2022-08-01 15:13:47 +00:00
}
2023-07-24 13:12:36 +00: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-01 15:13:47 +00:00
if ($variable === false || $variable->isEmpty()) {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
2022-08-01 15:13:47 +00:00
}
$variable
->setAttribute('key', $key)
2022-08-01 15:13:47 +00:00
->setAttribute('value', $value ?? $variable->getAttribute('value'))
2023-03-28 13:21:42 +00:00
->setAttribute('search', implode(' ', [$variableId, $function->getId(), $key, 'function']));
2022-08-01 15:13:47 +00:00
try {
$dbForProject->updateDocument('variables', $variable->getId(), $variable);
} catch (DuplicateException $th) {
throw new Exception(Exception::VARIABLE_ALREADY_EXISTS);
2022-08-01 15:13:47 +00:00
}
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false));
2022-08-01 15:13:47 +00:00
2023-07-28 07:56:07 +00:00
// Inform scheduler to pull the latest changes
2025-02-03 09:32:01 +00:00
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
2023-06-11 10:29:04 +00:00
$schedule
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $function->getAttribute('schedule'))
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
2025-02-03 09:32:01 +00:00
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
2023-06-11 10:29:04 +00:00
2022-08-01 15:13:47 +00:00
$response->dynamic($variable, Response::MODEL_VARIABLE);
});
2022-08-09 15:29:24 +00:00
App::delete('/v1/functions/:functionId/variables/:variableId')
2023-10-02 14:02:48 +00:00
->desc('Delete variable')
2022-08-01 15:13:47 +00:00
->groups(['api', 'functions'])
->label('scope', 'functions.write')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2022-09-05 08:00:08 +00:00
->label('audits.event', 'variable.delete')
2022-09-04 08:13:44 +00:00
->label('audits.resource', 'function/{request.functionId}')
2022-08-03 13:32:50 +00:00
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
2022-08-01 15:13:47 +00:00
->label('sdk.namespace', 'functions')
->label('sdk.method', 'deleteVariable')
->label('sdk.description', '/docs/references/functions/delete-variable.md')
2022-08-01 15:13:47 +00:00
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
2022-09-19 10:05:42 +00:00
->param('functionId', '', new UID(), 'Function unique ID.', false)
->param('variableId', '', new UID(), 'Variable unique ID.', false)
2022-08-01 15:13:47 +00:00
->inject('response')
->inject('dbForProject')
2025-02-03 09:32:01 +00:00
->inject('dbForPlatform')
->action(function (string $functionId, string $variableId, Response $response, Database $dbForProject, Database $dbForPlatform) {
2022-08-01 15:13:47 +00:00
$function = $dbForProject->getDocument('functions', $functionId);
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
2022-08-01 15:13:47 +00:00
}
2023-07-24 13:12:36 +00: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-01 15:13:47 +00:00
if ($variable === false || $variable->isEmpty()) {
throw new Exception(Exception::VARIABLE_NOT_FOUND);
2022-08-01 15:13:47 +00:00
}
$dbForProject->deleteDocument('variables', $variable->getId());
2023-06-11 10:29:04 +00:00
$dbForProject->updateDocument('functions', $function->getId(), $function->setAttribute('live', false));
2022-08-01 15:13:47 +00:00
2023-07-28 07:56:07 +00:00
// Inform scheduler to pull the latest changes
2025-02-03 09:32:01 +00:00
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
2023-06-11 10:29:04 +00:00
$schedule
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $function->getAttribute('schedule'))
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
2025-02-03 09:32:01 +00:00
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
2023-06-11 10:29:04 +00:00
2022-08-01 15:13:47 +00:00
$response->noContent();
});
2024-07-19 12:09:18 +00:00
App::get('/v1/functions/templates')
2024-08-13 12:59:37 +00:00
->groups(['api'])
2024-07-29 20:55:18 +00:00
->desc('List function templates')
2024-07-29 12:25:58 +00:00
->label('scope', 'public')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2025-02-03 09:32:01 +00:00
->label('sdk', new Method(
namespace: 'functions',
name: 'listTemplates',
description: '/docs/references/functions/list-templates.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_TEMPLATE_FUNCTION_LIST,
)
]
))
2024-07-29 13:29:16 +00:00
->param('runtimes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('runtimes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of runtimes allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' runtimes are allowed.', true)
2024-07-30 10:41:04 +00:00
->param('useCases', [], new ArrayList(new WhiteList(['dev-tools','starter','databases','ai','messaging','utilities']), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of use cases allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' use cases are allowed.', true)
2024-07-30 09:43:38 +00:00
->param('limit', 25, new Range(1, 5000), 'Limit the number of templates returned in the response. Default limit is 25, and maximum limit is 5000.', true)
->param('offset', 0, new Range(0, 5000), 'Offset the list of returned templates. Maximum offset is 5000.', true)
2024-07-19 12:09:18 +00:00
->inject('response')
2024-07-29 13:29:16 +00:00
->action(function (array $runtimes, array $usecases, int $limit, int $offset, Response $response) {
2024-07-29 12:25:58 +00:00
$templates = Config::getParam('function-templates', []);
2024-07-29 13:29:16 +00:00
if (!empty($runtimes)) {
$templates = \array_filter($templates, function ($template) use ($runtimes) {
return \count(\array_intersect($runtimes, \array_column($template['runtimes'], 'name'))) > 0;
});
}
2024-07-29 12:25:58 +00:00
if (!empty($usecases)) {
$templates = \array_filter($templates, function ($template) use ($usecases) {
2024-07-30 10:41:04 +00:00
return \count(\array_intersect($usecases, $template['useCases'])) > 0;
2024-07-29 12:25:58 +00:00
});
}
$responseTemplates = \array_slice($templates, $offset, $limit);
2024-07-19 12:09:18 +00:00
$response->dynamic(new Document([
2024-07-29 12:25:58 +00:00
'templates' => $responseTemplates,
'total' => \count($responseTemplates),
2024-07-26 12:17:03 +00:00
]), Response::MODEL_TEMPLATE_FUNCTION_LIST);
2024-07-19 12:09:18 +00:00
});
2024-07-30 11:46:45 +00:00
App::get('/v1/functions/templates/:templateId')
->desc('Get function template')
->label('scope', 'public')
2024-10-29 15:07:12 +00:00
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
2025-02-03 09:32:01 +00:00
->label('sdk', new Method(
namespace: 'functions',
name: 'getTemplate',
description: '/docs/references/functions/get-template.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_TEMPLATE_FUNCTION,
)
]
))
2024-07-30 11:46:45 +00:00
->param('templateId', '', new Text(128), 'Template ID.')
->inject('response')
->action(function (string $templateId, Response $response) {
$templates = Config::getParam('function-templates', []);
2024-11-04 08:49:20 +00:00
$filtered = \array_filter($templates, function ($template) use ($templateId) {
2024-07-30 11:46:45 +00:00
return $template['id'] === $templateId;
2024-11-04 08:49:20 +00:00
});
$template = array_shift($filtered);
2024-07-30 11:46:45 +00:00
if (empty($template)) {
throw new Exception(Exception::FUNCTION_TEMPLATE_NOT_FOUND);
}
$response->dynamic(new Document($template), Response::MODEL_TEMPLATE_FUNCTION);
});