mirror of
https://github.com/appwrite/appwrite
synced 2026-05-23 17:08:45 +00:00
Move function endpoints from controller to modules
This commit is contained in:
parent
ac51fcbf4e
commit
0dbb9dadf6
15 changed files with 1106 additions and 720 deletions
|
|
@ -14,9 +14,7 @@ use Appwrite\Platform\Tasks\ScheduleExecutions;
|
|||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Deployments;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Executions;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Executor\Executor;
|
||||
|
|
@ -50,716 +48,6 @@ use Utopia\Validator\WhiteList;
|
|||
|
||||
include_once __DIR__ . '/../shared/api.php';
|
||||
|
||||
App::get('/v1/functions/specifications')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('List available function runtime specifications')
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->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,
|
||||
)
|
||||
]
|
||||
))
|
||||
->inject('response')
|
||||
->inject('plan')
|
||||
->action(function (Response $response, array $plan) {
|
||||
$allRuntimeSpecs = Config::getParam('runtime-specifications', []);
|
||||
|
||||
$runtimeSpecs = [];
|
||||
foreach ($allRuntimeSpecs as $spec) {
|
||||
$spec['enabled'] = true;
|
||||
|
||||
if (array_key_exists('runtimeSpecifications', $plan)) {
|
||||
$spec['enabled'] = in_array($spec['slug'], $plan['runtimeSpecifications']);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'specifications' => $runtimeSpecs,
|
||||
'total' => count($runtimeSpecs)
|
||||
]), Response::MODEL_SPECIFICATION_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/functions/:functionId')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Get function')
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->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.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $functionId, Response $response, Database $dbForProject) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic($function, Response::MODEL_FUNCTION);
|
||||
});
|
||||
|
||||
App::get('/v1/functions/:functionId/usage')
|
||||
->desc('Get function usage')
|
||||
->groups(['api', 'functions', 'usage'])
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->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.')
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $functionId, string $range, Response $response, Database $dbForProject) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$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),
|
||||
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_MB_SECONDS),
|
||||
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS)
|
||||
];
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['inf'])
|
||||
]);
|
||||
|
||||
$stats[$metric]['total'] = $result['value'] ?? 0;
|
||||
$limit = $days['limit'];
|
||||
$period = $days['period'];
|
||||
$results = $dbForProject->find('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', [$period]),
|
||||
Query::limit($limit),
|
||||
Query::orderDesc('time'),
|
||||
]);
|
||||
$stats[$metric]['data'] = [];
|
||||
foreach ($results as $result) {
|
||||
$stats[$metric]['data'][$result->getAttribute('time')] = [
|
||||
'value' => $result->getAttribute('value'),
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$format = match ($days['period']) {
|
||||
'1h' => 'Y-m-d\TH:00:00.000P',
|
||||
'1d' => 'Y-m-d\T00:00:00.000P',
|
||||
};
|
||||
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'range' => $range,
|
||||
'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'],
|
||||
'buildsMbSecondsTotal' => $usage[$metrics[7]]['total'],
|
||||
'buildsMbSeconds' => $usage[$metrics[7]]['data'],
|
||||
'executionsMbSeconds' => $usage[$metrics[8]]['data'],
|
||||
'executionsMbSecondsTotal' => $usage[$metrics[8]]['total']
|
||||
]), Response::MODEL_USAGE_FUNCTION);
|
||||
});
|
||||
|
||||
App::get('/v1/functions/usage')
|
||||
->desc('Get functions usage')
|
||||
->groups(['api', 'functions', 'usage'])
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->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,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $range, Response $response, Database $dbForProject) {
|
||||
|
||||
$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,
|
||||
METRIC_BUILDS_MB_SECONDS,
|
||||
METRIC_EXECUTIONS_MB_SECONDS,
|
||||
];
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['inf'])
|
||||
]);
|
||||
|
||||
$stats[$metric]['total'] = $result['value'] ?? 0;
|
||||
$limit = $days['limit'];
|
||||
$period = $days['period'];
|
||||
$results = $dbForProject->find('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', [$period]),
|
||||
Query::limit($limit),
|
||||
Query::orderDesc('time'),
|
||||
]);
|
||||
$stats[$metric]['data'] = [];
|
||||
foreach ($results as $result) {
|
||||
$stats[$metric]['data'][$result->getAttribute('time')] = [
|
||||
'value' => $result->getAttribute('value'),
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$format = match ($days['period']) {
|
||||
'1h' => 'Y-m-d\TH:00:00.000P',
|
||||
'1d' => 'Y-m-d\T00:00:00.000P',
|
||||
};
|
||||
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
$response->dynamic(new Document([
|
||||
'range' => $range,
|
||||
'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'],
|
||||
'buildsMbSecondsTotal' => $usage[$metrics[8]]['total'],
|
||||
'buildsMbSeconds' => $usage[$metrics[8]]['data'],
|
||||
'executionsMbSeconds' => $usage[$metrics[9]]['data'],
|
||||
'executionsMbSecondsTotal' => $usage[$metrics[9]]['total'],
|
||||
]), Response::MODEL_USAGE_FUNCTIONS);
|
||||
});
|
||||
|
||||
App::get('/v1/functions/:functionId/deployments/:deploymentId/download')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Download deployment')
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->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
|
||||
))
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->inject('response')
|
||||
->inject('request')
|
||||
->inject('dbForProject')
|
||||
->inject('deviceForFunctions')
|
||||
->action(function (string $functionId, string $deploymentId, Response $response, Request $request, Database $dbForProject, Device $deviceForFunctions) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
if ($deployment->isEmpty()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $deployment->getAttribute('path', '');
|
||||
if (!$deviceForFunctions->exists($path)) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response
|
||||
->setContentType('application/gzip')
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->addHeader('X-Peak', \memory_get_peak_usage())
|
||||
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"');
|
||||
|
||||
$size = $deviceForFunctions->getFileSize($path);
|
||||
$rangeHeader = $request->getHeader('range');
|
||||
|
||||
if (!empty($rangeHeader)) {
|
||||
$start = $request->getRangeStart();
|
||||
$end = $request->getRangeEnd();
|
||||
$unit = $request->getRangeUnit();
|
||||
|
||||
if ($end === null) {
|
||||
$end = min(($start + MAX_OUTPUT_CHUNK_SIZE - 1), ($size - 1));
|
||||
}
|
||||
|
||||
if ($unit !== 'bytes' || $start >= $end || $end >= $size) {
|
||||
throw new Exception(Exception::STORAGE_INVALID_RANGE);
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Accept-Ranges', 'bytes')
|
||||
->addHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $size)
|
||||
->addHeader('Content-Length', $end - $start + 1)
|
||||
->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT);
|
||||
|
||||
$response->send($deviceForFunctions->read($path, $start, ($end - $start + 1)));
|
||||
}
|
||||
|
||||
if ($size > APP_STORAGE_READ_BUFFER) {
|
||||
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
||||
$response->chunk(
|
||||
$deviceForFunctions->read(
|
||||
$path,
|
||||
($i * MAX_OUTPUT_CHUNK_SIZE),
|
||||
min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))
|
||||
),
|
||||
(($i + 1) * MAX_OUTPUT_CHUNK_SIZE) >= $size
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$response->send($deviceForFunctions->read($path));
|
||||
}
|
||||
});
|
||||
|
||||
App::patch('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Update deployment')
|
||||
->label('scope', 'functions.write')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('event', 'functions.[functionId].deployments.[deploymentId].update')
|
||||
->label('audits.event', 'deployment.update')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'updateDeployment')
|
||||
->label('sdk.description', '/docs/references/functions/update-function-deployment.md')
|
||||
->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.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, Database $dbForPlatform) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($deployment->isEmpty()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($build->isEmpty()) {
|
||||
throw new Exception(Exception::BUILD_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($build->getAttribute('status') !== 'ready') {
|
||||
throw new Exception(Exception::BUILD_NOT_READY);
|
||||
}
|
||||
|
||||
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
|
||||
'deploymentInternalId' => $deployment->getInternalId(),
|
||||
'deployment' => $deployment->getId(),
|
||||
])));
|
||||
|
||||
// Inform scheduler if function is still active
|
||||
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('functionId', $function->getId())
|
||||
->setParam('deploymentId', $deployment->getId());
|
||||
|
||||
$response->dynamic($function, Response::MODEL_FUNCTION);
|
||||
});
|
||||
|
||||
App::delete('/v1/functions/:functionId')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Delete function')
|
||||
->label('scope', 'functions.write')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('event', 'functions.[functionId].delete')
|
||||
->label('audits.event', 'function.delete')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->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.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDeletes')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $functionId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Database $dbForPlatform) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$dbForProject->deleteDocument('functions', $function->getId())) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove function from DB');
|
||||
}
|
||||
|
||||
// Inform scheduler to no longer run function
|
||||
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('active', false);
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($function);
|
||||
|
||||
$queueForEvents->setParam('functionId', $function->getId());
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::get('/v1/functions/:functionId/deployments')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('List deployments')
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->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.')
|
||||
->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)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $functionId, array $queries, string $search, Response $response, Database $dbForProject) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Set resource queries
|
||||
$queries[] = Query::equal('resourceInternalId', [$function->getInternalId()]);
|
||||
$queries[] = Query::equal('resourceType', ['functions']);
|
||||
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$deploymentId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Deployment '{$deploymentId}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
$results = $dbForProject->find('deployments', $queries);
|
||||
$total = $dbForProject->count('deployments', $filterQueries, APP_LIMIT_COUNT);
|
||||
|
||||
foreach ($results as $result) {
|
||||
$build = $dbForProject->getDocument('builds', $result->getAttribute('buildId', ''));
|
||||
$result->setAttribute('status', $build->getAttribute('status', 'processing'));
|
||||
$result->setAttribute('buildLogs', $build->getAttribute('logs', ''));
|
||||
$result->setAttribute('buildTime', $build->getAttribute('duration', 0));
|
||||
$result->setAttribute('buildSize', $build->getAttribute('size', 0));
|
||||
$result->setAttribute('size', $result->getAttribute('size', 0));
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'deployments' => $results,
|
||||
'total' => $total,
|
||||
]), Response::MODEL_DEPLOYMENT_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Get deployment')
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->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.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
|
||||
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($deployment->isEmpty()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
|
||||
$deployment->setAttribute('status', $build->getAttribute('status', 'waiting'));
|
||||
$deployment->setAttribute('buildLogs', $build->getAttribute('logs', ''));
|
||||
$deployment->setAttribute('buildTime', $build->getAttribute('duration', 0));
|
||||
$deployment->setAttribute('buildSize', $build->getAttribute('size', 0));
|
||||
$deployment->setAttribute('size', $deployment->getAttribute('size', 0));
|
||||
|
||||
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
|
||||
});
|
||||
|
||||
App::delete('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Delete deployment')
|
||||
->label('scope', 'functions.write')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('event', 'functions.[functionId].deployments.[deploymentId].delete')
|
||||
->label('audits.event', 'deployment.delete')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->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.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDeletes')
|
||||
->inject('queueForEvents')
|
||||
->inject('deviceForFunctions')
|
||||
->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Delete $queueForDeletes, Event $queueForEvents, Device $deviceForFunctions) {
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
if (!$dbForProject->deleteDocument('deployments', $deployment->getId())) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove deployment from DB');
|
||||
}
|
||||
|
||||
if (!empty($deployment->getAttribute('path', ''))) {
|
||||
if (!($deviceForFunctions->delete($deployment->getAttribute('path', '')))) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove deployment from storage');
|
||||
}
|
||||
}
|
||||
|
||||
if ($function->getAttribute('deployment') === $deployment->getId()) { // Reset function deployment
|
||||
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
|
||||
'deployment' => '',
|
||||
'deploymentInternalId' => '',
|
||||
])));
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('functionId', $function->getId())
|
||||
->setParam('deploymentId', $deployment->getId());
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($deployment);
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::post('/v1/functions/:functionId/deployments/:deploymentId/build')
|
||||
->alias('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId')
|
||||
->groups(['api', 'functions'])
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Functions\Http\Deployments;
|
||||
|
||||
use Appwrite\Event\Delete as DeleteEvent;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Storage\Device;
|
||||
|
||||
class Delete extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'deleteDeployment';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_DELETE)
|
||||
->setHttpPath('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
->desc('Delete deployment')
|
||||
->groups(['api', 'functions'])
|
||||
->label('scope', 'functions.write')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('event', 'functions.[functionId].deployments.[deploymentId].delete')
|
||||
->label('audits.event', 'deployment.delete')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
name: 'deleteDeployment',
|
||||
description: <<<EOT
|
||||
Delete a code deployment by its unique ID.
|
||||
EOT,
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::NONE
|
||||
))
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDeletes')
|
||||
->inject('queueForEvents')
|
||||
->inject('deviceForFunctions')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $siteId, string $deploymentId, Response $response, Database $dbForProject, DeleteEvent $queueForDeletes, Event $queueForEvents, Device $deviceForSites)
|
||||
{
|
||||
$site = $dbForProject->getDocument('sites', $siteId);
|
||||
if ($site->isEmpty()) {
|
||||
throw new Exception(Exception::SITE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
if ($deployment->isEmpty()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($deployment->getAttribute('resourceId') !== $site->getId()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$dbForProject->deleteDocument('deployments', $deployment->getId())) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove deployment from DB');
|
||||
}
|
||||
|
||||
if (!empty($deployment->getAttribute('path', ''))) {
|
||||
if (!($deviceForSites->delete($deployment->getAttribute('path', '')))) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove deployment from storage');
|
||||
}
|
||||
}
|
||||
|
||||
if ($site->getAttribute('deployment') === $deployment->getId()) { // Reset site deployment
|
||||
$site = $dbForProject->updateDocument('sites', $site->getId(), new Document(array_merge($site->getArrayCopy(), [
|
||||
'deploymentId' => '',
|
||||
'deploymentInternalId' => '',
|
||||
])));
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('siteId', $site->getId())
|
||||
->setParam('deploymentId', $deployment->getId());
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($deployment);
|
||||
|
||||
$response->noContent();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Functions\Http\Deployments\Download;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\Swoole\Request;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'getDeploymentDownload';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/functions/:functionId/deployments/:deploymentId/download')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Download deployment')
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
name: 'getDeploymentDownload',
|
||||
description: <<<EOT
|
||||
Get a function deployment content by its unique ID. The endpoint response return with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to user downloads directory.
|
||||
EOT,
|
||||
auth: [AuthType::KEY, AuthType::JWT],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::ANY,
|
||||
type: MethodType::LOCATION
|
||||
))
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->inject('response')
|
||||
->inject('request')
|
||||
->inject('dbForProject')
|
||||
->inject('deviceForFunctions')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $functionId, string $deploymentId, Response $response, Request $request, Database $dbForProject, Device $deviceForFunctions)
|
||||
{
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
if ($deployment->isEmpty()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $deployment->getAttribute('path', '');
|
||||
if (!$deviceForFunctions->exists($path)) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response
|
||||
->setContentType('application/gzip')
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->addHeader('X-Peak', \memory_get_peak_usage())
|
||||
->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"');
|
||||
|
||||
$size = $deviceForFunctions->getFileSize($path);
|
||||
$rangeHeader = $request->getHeader('range');
|
||||
|
||||
if (!empty($rangeHeader)) {
|
||||
$start = $request->getRangeStart();
|
||||
$end = $request->getRangeEnd();
|
||||
$unit = $request->getRangeUnit();
|
||||
|
||||
if ($end === null) {
|
||||
$end = min(($start + MAX_OUTPUT_CHUNK_SIZE - 1), ($size - 1));
|
||||
}
|
||||
|
||||
if ($unit !== 'bytes' || $start >= $end || $end >= $size) {
|
||||
throw new Exception(Exception::STORAGE_INVALID_RANGE);
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Accept-Ranges', 'bytes')
|
||||
->addHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $size)
|
||||
->addHeader('Content-Length', $end - $start + 1)
|
||||
->setStatusCode(Response::STATUS_CODE_PARTIALCONTENT);
|
||||
|
||||
$response->send($deviceForFunctions->read($path, $start, ($end - $start + 1)));
|
||||
}
|
||||
|
||||
if ($size > APP_STORAGE_READ_BUFFER) {
|
||||
for ($i = 0; $i < ceil($size / MAX_OUTPUT_CHUNK_SIZE); $i++) {
|
||||
$response->chunk(
|
||||
$deviceForFunctions->read(
|
||||
$path,
|
||||
($i * MAX_OUTPUT_CHUNK_SIZE),
|
||||
min(MAX_OUTPUT_CHUNK_SIZE, $size - ($i * MAX_OUTPUT_CHUNK_SIZE))
|
||||
),
|
||||
(($i + 1) * MAX_OUTPUT_CHUNK_SIZE) >= $size
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$response->send($deviceForFunctions->read($path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Functions\Http\Deployments;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'getDeployment';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
->desc('Get deployment')
|
||||
->groups(['api', 'functions'])
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
name: 'getDeployment',
|
||||
description: <<<EOT
|
||||
Get a function deployment by its unique ID.
|
||||
EOT,
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_DEPLOYMENT,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $functionId, string $deploymentId, Response $response, Database $dbForProject)
|
||||
{
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
|
||||
if ($deployment->getAttribute('resourceId') !== $function->getId()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($deployment->isEmpty()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
|
||||
$deployment->setAttribute('status', $build->getAttribute('status', 'waiting'));
|
||||
$deployment->setAttribute('buildLogs', $build->getAttribute('logs', ''));
|
||||
$deployment->setAttribute('buildTime', $build->getAttribute('duration', 0));
|
||||
$deployment->setAttribute('buildSize', $build->getAttribute('size', 0));
|
||||
$deployment->setAttribute('size', $deployment->getAttribute('size', 0));
|
||||
|
||||
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Functions\Http\Deployments;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'updateDeployment';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
|
||||
->setHttpPath('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
->desc('Update deployment')
|
||||
->groups(['api', 'functions'])
|
||||
->label('scope', 'functions.write')
|
||||
->label('event', 'functions.[functionId].deployments.[deploymentId].update')
|
||||
->label('audits.event', 'deployment.update')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
name: 'updateDeployment',
|
||||
description: <<<EOT
|
||||
Update the function active deployment. Use this endpoint to switch the code deployment that should be used when visitor opens your function.
|
||||
EOT,
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_FUNCTION,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $functionId, string $deploymentId, Response $response, Database $dbForProject, Event $queueForEvents, Database $dbForPlatform)
|
||||
{
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($deployment->isEmpty()) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($build->isEmpty()) {
|
||||
throw new Exception(Exception::BUILD_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($build->getAttribute('status') !== 'ready') {
|
||||
throw new Exception(Exception::BUILD_NOT_READY);
|
||||
}
|
||||
|
||||
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
|
||||
'deploymentInternalId' => $deployment->getInternalId(),
|
||||
'deployment' => $deployment->getId(),
|
||||
])));
|
||||
|
||||
// Inform scheduler if function is still active
|
||||
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('functionId', $function->getId())
|
||||
->setParam('deploymentId', $deployment->getId());
|
||||
|
||||
$response->dynamic($function, Response::MODEL_FUNCTION);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Functions\Http\Deployments;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Deployments;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class XList extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'listDeployments';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/functions/:functionId/deployments')
|
||||
->desc('List deployments')
|
||||
->groups(['api', 'functions'])
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
name: 'listDeployments',
|
||||
description: <<<EOT
|
||||
Get a list of all the function's code deployments. You can use the query params to filter your results.
|
||||
EOT,
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_DEPLOYMENT_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->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)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $functionId, array $queries, string $search, Response $response, Database $dbForProject)
|
||||
{
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
// Set resource queries
|
||||
$queries[] = Query::equal('resourceInternalId', [$function->getInternalId()]);
|
||||
$queries[] = Query::equal('resourceType', ['functions']);
|
||||
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
*/
|
||||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
$cursor = reset($cursor);
|
||||
if ($cursor) {
|
||||
/** @var Query $cursor */
|
||||
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$deploymentId = $cursor->getValue();
|
||||
$cursorDocument = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Deployment '{$deploymentId}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
$results = $dbForProject->find('deployments', $queries);
|
||||
$total = $dbForProject->count('deployments', $filterQueries, APP_LIMIT_COUNT);
|
||||
|
||||
foreach ($results as $result) {
|
||||
$build = $dbForProject->getDocument('builds', $result->getAttribute('buildId', ''));
|
||||
$result->setAttribute('status', $build->getAttribute('status', 'processing'));
|
||||
$result->setAttribute('buildLogs', $build->getAttribute('logs', ''));
|
||||
$result->setAttribute('buildTime', $build->getAttribute('duration', 0));
|
||||
$result->setAttribute('buildSize', $build->getAttribute('size', 0));
|
||||
$result->setAttribute('size', $result->getAttribute('size', 0));
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'deployments' => $results,
|
||||
'total' => $total,
|
||||
]), Response::MODEL_DEPLOYMENT_LIST);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Functions\Http\Functions;
|
||||
|
||||
use Appwrite\Event\Delete as DeleteEvent;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
||||
class Delete extends Base
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'deleteFunction';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_DELETE)
|
||||
->setHttpPath('/v1/functions/:functionId')
|
||||
->desc('Delete function')
|
||||
->groups(['api', 'functions'])
|
||||
->label('scope', 'functions.write')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('event', 'functions.[functionId].delete')
|
||||
->label('audits.event', 'function.delete')
|
||||
->label('audits.resource', 'function/{request.functionId}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
name: 'delete',
|
||||
description: <<<EOT
|
||||
Delete a function by its unique ID.
|
||||
EOT,
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::NONE
|
||||
))
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForDeletes')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $functionId, Response $response, Database $dbForProject, DeleteEvent $queueForDeletes, Event $queueForEvents, Database $dbForPlatform)
|
||||
{
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$dbForProject->deleteDocument('functions', $function->getId())) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove function from DB');
|
||||
}
|
||||
|
||||
// Inform scheduler to no longer run function
|
||||
$schedule = $dbForPlatform->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('active', false);
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($function);
|
||||
|
||||
$queueForEvents->setParam('functionId', $function->getId());
|
||||
|
||||
$response->noContent();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Functions\Http\Functions;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
||||
class Get extends Base
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'getFunction';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/functions/:functionId')
|
||||
->desc('Get function')
|
||||
->groups(['api', 'functions'])
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
name: 'get',
|
||||
description: <<<EOT
|
||||
Get a function by its unique ID.
|
||||
EOT,
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_FUNCTION,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $functionId, Response $response, Database $dbForProject)
|
||||
{
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic($function, Response::MODEL_FUNCTION);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Functions\Http\Specifications;
|
||||
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
|
||||
class XList extends Base
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'listFunctionsSpecifications';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/functions/specifications')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('List available function runtime specifications')
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
name: 'listSpecifications',
|
||||
description: <<<EOT
|
||||
List allowed function specifications for this instance.
|
||||
EOT,
|
||||
auth: [AuthType::KEY, AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_SPECIFICATION_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->inject('response')
|
||||
->inject('plan')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(Response $response, array $plan)
|
||||
{
|
||||
$allRuntimeSpecs = Config::getParam('runtime-specifications', []);
|
||||
|
||||
$runtimeSpecs = [];
|
||||
foreach ($allRuntimeSpecs as $spec) {
|
||||
$spec['enabled'] = true;
|
||||
|
||||
if (array_key_exists('runtimeSpecifications', $plan)) {
|
||||
$spec['enabled'] = in_array($spec['slug'], $plan['runtimeSpecifications']);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'specifications' => $runtimeSpecs,
|
||||
'total' => count($runtimeSpecs)
|
||||
]), Response::MODEL_SPECIFICATION_LIST);
|
||||
}
|
||||
}
|
||||
149
src/Appwrite/Platform/Modules/Functions/Http/Usage/Get.php
Normal file
149
src/Appwrite/Platform/Modules/Functions/Http/Usage/Get.php
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Functions\Http\Usage;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Base
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'getFunctionUsage';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/functions/:functionId/usage')
|
||||
->desc('Get function usage')
|
||||
->groups(['api', 'functions', 'usage'])
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
name: 'getUsage',
|
||||
description: <<<EOT
|
||||
Get usage metrics and statistics for a for a specific function. View statistics including total deployments, builds, executions, storage usage, and compute time. The response includes both current totals and historical data for each metric. Use the optional range parameter to specify the time window for historical data: 24h (last 24 hours), 30d (last 30 days), or 90d (last 90 days). If not specified, defaults to 30 days.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_USAGE_FUNCTION,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('functionId', '', new UID(), 'Function ID.')
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $functionId, string $range, Response $response, Database $dbForProject)
|
||||
{
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$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),
|
||||
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_BUILDS_MB_SECONDS),
|
||||
str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS)
|
||||
];
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['inf'])
|
||||
]);
|
||||
|
||||
$stats[$metric]['total'] = $result['value'] ?? 0;
|
||||
$limit = $days['limit'];
|
||||
$period = $days['period'];
|
||||
$results = $dbForProject->find('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', [$period]),
|
||||
Query::limit($limit),
|
||||
Query::orderDesc('time'),
|
||||
]);
|
||||
$stats[$metric]['data'] = [];
|
||||
foreach ($results as $result) {
|
||||
$stats[$metric]['data'][$result->getAttribute('time')] = [
|
||||
'value' => $result->getAttribute('value'),
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$format = match ($days['period']) {
|
||||
'1h' => 'Y-m-d\TH:00:00.000P',
|
||||
'1d' => 'Y-m-d\T00:00:00.000P',
|
||||
};
|
||||
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'range' => $range,
|
||||
'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'],
|
||||
'buildsMbSecondsTotal' => $usage[$metrics[7]]['total'],
|
||||
'buildsMbSeconds' => $usage[$metrics[7]]['data'],
|
||||
'executionsMbSeconds' => $usage[$metrics[8]]['data'],
|
||||
'executionsMbSecondsTotal' => $usage[$metrics[8]]['total']
|
||||
]), Response::MODEL_USAGE_FUNCTION);
|
||||
}
|
||||
}
|
||||
142
src/Appwrite/Platform/Modules/Functions/Http/Usage/XList.php
Normal file
142
src/Appwrite/Platform/Modules/Functions/Http/Usage/XList.php
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Functions\Http\Usage;
|
||||
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class XList extends Base
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'getFunctionsUsage';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/functions/usage')
|
||||
->desc('Get functions usage')
|
||||
->groups(['api', 'functions', 'usage'])
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'functions',
|
||||
name: 'listUsage',
|
||||
description: <<<EOT
|
||||
Get usage metrics and statistics for all functions in the project. View statistics including total deployments, builds, logs, storage usage, and compute time. The response includes both current totals and historical data for each metric. Use the optional range parameter to specify the time window for historical data: 24h (last 24 hours), 30d (last 30 days), or 90d (last 90 days). If not specified, defaults to 30 days.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_USAGE_FUNCTIONS,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $range, Response $response, Database $dbForProject)
|
||||
{
|
||||
$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,
|
||||
METRIC_BUILDS_MB_SECONDS,
|
||||
METRIC_EXECUTIONS_MB_SECONDS,
|
||||
];
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', ['inf'])
|
||||
]);
|
||||
|
||||
$stats[$metric]['total'] = $result['value'] ?? 0;
|
||||
$limit = $days['limit'];
|
||||
$period = $days['period'];
|
||||
$results = $dbForProject->find('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::equal('period', [$period]),
|
||||
Query::limit($limit),
|
||||
Query::orderDesc('time'),
|
||||
]);
|
||||
$stats[$metric]['data'] = [];
|
||||
foreach ($results as $result) {
|
||||
$stats[$metric]['data'][$result->getAttribute('time')] = [
|
||||
'value' => $result->getAttribute('value'),
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$format = match ($days['period']) {
|
||||
'1h' => 'Y-m-d\TH:00:00.000P',
|
||||
'1d' => 'Y-m-d\T00:00:00.000P',
|
||||
};
|
||||
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
$response->dynamic(new Document([
|
||||
'range' => $range,
|
||||
'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'],
|
||||
'buildsMbSecondsTotal' => $usage[$metrics[8]]['total'],
|
||||
'buildsMbSeconds' => $usage[$metrics[8]]['data'],
|
||||
'executionsMbSeconds' => $usage[$metrics[9]]['data'],
|
||||
'executionsMbSecondsTotal' => $usage[$metrics[9]]['total'],
|
||||
]), Response::MODEL_USAGE_FUNCTIONS);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,12 +3,22 @@
|
|||
namespace Appwrite\Platform\Modules\Functions\Services;
|
||||
|
||||
use Appwrite\Platform\Modules\Functions\Http\Deployments\Create as CreateDeployment;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Deployments\Delete as DeleteDeployment;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Deployments\Download\Get as DownloadDeployment;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Deployments\Get as GetDeployment;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Deployments\Template\Create as CreateTemplateDeployment;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Deployments\Update as UpdateDeployment;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Deployments\Vcs\Create as CreateVcsDeployment;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Deployments\XList as ListDeployments;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Functions\Create as CreateFunction;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Functions\Delete as DeleteFunction;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Functions\Get as GetFunction;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Functions\Update as UpdateFunction;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Functions\XList as ListFunctions;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Runtimes\XList as ListRuntimes;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Specifications\XList as ListSpecifications;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Usage\Get as GetUsage;
|
||||
use Appwrite\Platform\Modules\Functions\Http\Usage\XList as ListUsage;
|
||||
use Utopia\Platform\Service;
|
||||
|
||||
class Http extends Service
|
||||
|
|
@ -16,12 +26,32 @@ class Http extends Service
|
|||
public function __construct()
|
||||
{
|
||||
$this->type = Service::TYPE_HTTP;
|
||||
|
||||
// Functions
|
||||
$this->addAction(CreateFunction::getName(), new CreateFunction());
|
||||
$this->addAction(GetFunction::getName(), new GetFunction());
|
||||
$this->addAction(UpdateFunction::getName(), new UpdateFunction());
|
||||
$this->addAction(ListFunctions::getName(), new ListFunctions());
|
||||
$this->addAction(DeleteFunction::getName(), new DeleteFunction());
|
||||
|
||||
// Runtimes
|
||||
$this->addAction(ListRuntimes::getName(), new ListRuntimes());
|
||||
|
||||
// Specifications
|
||||
$this->addAction(ListSpecifications::getName(), new ListSpecifications());
|
||||
|
||||
// Deployments
|
||||
$this->addAction(CreateDeployment::getName(), new CreateDeployment());
|
||||
$this->addAction(GetDeployment::getName(), new GetDeployment());
|
||||
$this->addAction(UpdateDeployment::getName(), new UpdateDeployment());
|
||||
$this->addAction(ListDeployments::getName(), new ListDeployments());
|
||||
$this->addAction(DeleteDeployment::getName(), new DeleteDeployment());
|
||||
$this->addAction(CreateTemplateDeployment::getName(), new CreateTemplateDeployment());
|
||||
$this->addAction(CreateVcsDeployment::getName(), new CreateVcsDeployment());
|
||||
$this->addAction(DownloadDeployment::getName(), new DownloadDeployment());
|
||||
|
||||
// Usage
|
||||
$this->addAction(GetUsage::getName(), new GetUsage());
|
||||
$this->addAction(ListUsage::getName(), new ListUsage());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ use Appwrite\SDK\Method;
|
|||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
|
@ -47,13 +46,11 @@ class Get extends Action
|
|||
->param('siteId', '', new UID(), 'Site ID.')
|
||||
->param('deploymentId', '', new UID(), 'Deployment ID.')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $siteId, string $deploymentId, Response $response, Document $project, Database $dbForProject, Database $dbForPlatform)
|
||||
public function action(string $siteId, string $deploymentId, Response $response, Database $dbForProject)
|
||||
{
|
||||
$site = $dbForProject->getDocument('sites', $siteId);
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class Update extends Action
|
|||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_FUNCTION,
|
||||
model: Response::MODEL_SITE,
|
||||
)
|
||||
]
|
||||
))
|
||||
|
|
|
|||
|
|
@ -53,13 +53,11 @@ class XList extends Action
|
|||
->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)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $siteId, array $queries, string $search, Response $response, Document $project, Database $dbForProject, Database $dbForPlatform)
|
||||
public function action(string $siteId, array $queries, string $search, Response $response, Database $dbForProject)
|
||||
{
|
||||
$site = $dbForProject->getDocument('sites', $siteId);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue