mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Move Build data into it's own collection
Move Build data into it's own collection
This commit is contained in:
@@ -1914,6 +1914,17 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'buildId',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => 'entrypoint',
|
||||
@@ -2022,6 +2033,121 @@ $collections = [
|
||||
],
|
||||
],
|
||||
|
||||
'builds' => [
|
||||
'$collection' => Database::METADATA,
|
||||
'$id' => 'builds',
|
||||
'name' => 'Builds',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => 'dateCreated',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'status',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => 'pending',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'outputPath',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 2048,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'stderr',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'stdout',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'buildTime',
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'envVars',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => new stdClass(),
|
||||
'array' => false,
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => 'sourceType',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 2048,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => 'local',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'source',
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 2048,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => '_key_status',
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['status'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
],
|
||||
],
|
||||
'executions' => [
|
||||
'$collection' => Database::METADATA,
|
||||
'$id' => 'executions',
|
||||
|
||||
@@ -312,10 +312,12 @@ App::patch('/v1/functions/:functionId/tag')
|
||||
->inject('response')
|
||||
->inject('dbForInternal')
|
||||
->inject('project')
|
||||
->action(function ($functionId, $tag, $response, $dbForInternal, $project) {
|
||||
->inject('user')
|
||||
->action(function ($functionId, $tag, $response, $dbForInternal, $project, $user) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForInternal */
|
||||
/** @var Utopia\Database\Document $project */
|
||||
/** @var Utopia\Database\Document $user */
|
||||
|
||||
$function = $dbForInternal->getDocument('functions', $functionId);
|
||||
|
||||
@@ -324,7 +326,8 @@ App::patch('/v1/functions/:functionId/tag')
|
||||
\curl_setopt($ch, CURLOPT_POST, true);
|
||||
\curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
|
||||
'functionId' => $functionId,
|
||||
'tagId' => $tag
|
||||
'tagId' => $tag,
|
||||
'userId' => $user->getId()
|
||||
]));
|
||||
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
\curl_setopt($ch, CURLOPT_TIMEOUT, 900);
|
||||
@@ -567,6 +570,15 @@ App::get('/v1/functions/:functionId/tags')
|
||||
$results = $dbForInternal->find('tags', $queries, $limit, $offset, [], [$orderType], $cursorTag ?? null, $cursorDirection);
|
||||
$sum = $dbForInternal->count('tags', $queries, APP_LIMIT_COUNT);
|
||||
|
||||
// Get Current Build Data
|
||||
foreach ($results as &$tag) {
|
||||
$build = $dbForInternal->getDocument('builds', $tag->getAttribute('buildId', ''));
|
||||
|
||||
$tag['status'] = $build->getAttribute('status', 'pending');
|
||||
$tag['buildStdout'] = $build->getAttribute('stdout', '');
|
||||
$tag['buildStderr'] = $build->getAttribute('stderr', '');
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'tags' => $results,
|
||||
'sum' => $sum,
|
||||
@@ -947,3 +959,78 @@ App::get('/v1/functions/:functionId/executions/:executionId')
|
||||
|
||||
$response->dynamic($execution, Response::MODEL_EXECUTION);
|
||||
});
|
||||
|
||||
App::get('/v1/builds')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Get Builds')
|
||||
->label('scope', 'execution.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'listBuilds')
|
||||
->label('sdk.description', '/docs/references/functions/list-builds.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_BUILD_LIST)
|
||||
->param('limit', 25, new Range(0, 100), 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
|
||||
->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('cursor', '', new UID(), 'ID of the build used as the starting point for the query, excluding the build itself. Should be used for efficient pagination when working with large sets of data.', true)
|
||||
->param('cursorDirection', Database::CURSOR_AFTER, new WhiteList([Database::CURSOR_AFTER, Database::CURSOR_BEFORE]), 'Direction of the cursor.', true)
|
||||
->inject('response')
|
||||
->inject('dbForInternal')
|
||||
->action(function ($limit, $offset, $search, $cursor, $cursorDirection, $response, $dbForInternal) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForInternal */
|
||||
|
||||
if (!empty($cursor)) {
|
||||
$cursorExecution = $dbForInternal->getDocument('builds', $cursor);
|
||||
|
||||
if ($cursorExecution->isEmpty()) {
|
||||
throw new Exception("Execution '{$cursor}' for the 'cursor' value not found.", 400);
|
||||
}
|
||||
}
|
||||
|
||||
$queries = [];
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
|
||||
}
|
||||
|
||||
$results = $dbForInternal->find('builds', $queries, $limit, $offset, [], [Database::ORDER_DESC], $cursorExecution ?? null, $cursorDirection);
|
||||
|
||||
$sum = $dbForInternal->count('builds', $queries, APP_LIMIT_COUNT);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'builds' => $results,
|
||||
'sum' => $sum,
|
||||
]), Response::MODEL_BUILD_LIST);
|
||||
});
|
||||
|
||||
App::get('/v1/builds/:buildId')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('Get Build')
|
||||
->label('scope', 'execution.read')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
|
||||
->label('sdk.namespace', 'functions')
|
||||
->label('sdk.method', 'getBuild')
|
||||
->label('sdk.description', '/docs/references/functions/get-build.md')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_BUILD)
|
||||
->param('buildId', '', new UID(), 'Build unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForInternal')
|
||||
->action(function ($buildId, $response, $dbForInternal) {
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Database $dbForInternal */
|
||||
|
||||
Authorization::disable();
|
||||
$build = $dbForInternal->getDocument('builds', $buildId);
|
||||
Authorization::reset();
|
||||
|
||||
if ($build->isEmpty()) {
|
||||
throw new Exception('Build not found', 404);
|
||||
}
|
||||
|
||||
$response->dynamic($build, Response::MODEL_BUILD);
|
||||
});
|
||||
+158
-88
@@ -211,10 +211,11 @@ App::post('/v1/cleanup/tag')
|
||||
App::post('/v1/tag')
|
||||
->param('functionId', '', new UID(), 'Function unique ID.')
|
||||
->param('tagId', '', new UID(), 'Tag unique ID.')
|
||||
->param('userId', '', new UID(), 'User unique ID.')
|
||||
->inject('response')
|
||||
->inject('dbForInternal')
|
||||
->inject('projectID')
|
||||
->action(function ($functionId, $tagId, $response, $dbForInternal, $projectID) {
|
||||
->action(function ($functionId, $tagId, $userId, $response, $dbForInternal, $projectID) {
|
||||
// Get function document
|
||||
$function = Authorization::skip(function () use ($functionId, $dbForInternal) {
|
||||
return $dbForInternal->getDocument('functions', $functionId);
|
||||
@@ -239,6 +240,31 @@ App::post('/v1/tag')
|
||||
$cron = (empty($function->getAttribute('tag')) && !empty($schedule)) ? new CronExpression($schedule) : null;
|
||||
$next = (empty($function->getAttribute('tag')) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : 0;
|
||||
|
||||
// Create a new build entry
|
||||
$buildId = $dbForInternal->getId();
|
||||
Authorization::skip(function () use ($buildId, $dbForInternal, $tag, $userId) {
|
||||
$dbForInternal->createDocument('builds', new Document([
|
||||
'$id' => $buildId,
|
||||
'$read' => (!empty($userId)) ? ['user:' . $userId] : [],
|
||||
'$write' => [],
|
||||
'dateCreated' => time(),
|
||||
'status' => 'pending',
|
||||
'outputPath' => '',
|
||||
'source' => $tag->getAttribute('path'),
|
||||
'sourceType' => Storage::DEVICE_LOCAL,
|
||||
'stdout' => '',
|
||||
'stderr' => '',
|
||||
'buildTime' => 0,
|
||||
'envVars' => [
|
||||
'APPWRITE_ENTRYPOINT_NAME' => $tag->getAttribute('entrypoint'),
|
||||
]
|
||||
]));
|
||||
|
||||
$tag->setAttribute('buildId', $buildId);
|
||||
|
||||
$dbForInternal->updateDocument('tags', $tag->getId(), $tag);
|
||||
});
|
||||
|
||||
// Update the function document setting the tag as the active one
|
||||
$function = Authorization::skip(function () use ($function, $dbForInternal, $tag, $next) {
|
||||
return $function = $dbForInternal->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
|
||||
@@ -248,12 +274,12 @@ App::post('/v1/tag')
|
||||
});
|
||||
|
||||
// Build Code
|
||||
go(function () use ($dbForInternal, $projectID, $function, $tagId, $functionId) {
|
||||
go(function () use ($dbForInternal, $projectID, $function, $tagId, $buildId, $functionId) {
|
||||
// Build Code
|
||||
$tag = runBuildStage($tagId, $function, $projectID, $dbForInternal);
|
||||
runBuildStage($buildId, $function, $projectID, $dbForInternal);
|
||||
|
||||
// Deploy Runtime Server
|
||||
createRuntimeServer($functionId, $projectID, $tag, $dbForInternal);
|
||||
createRuntimeServer($functionId, $projectID, $tagId, $dbForInternal);
|
||||
});
|
||||
|
||||
if (false === $function) {
|
||||
@@ -276,7 +302,7 @@ App::get('/v1/healthz')
|
||||
}
|
||||
);
|
||||
|
||||
function runBuildStage(string $tagID, Document $function, string $projectID, Database $database): Document
|
||||
function runBuildStage(string $buildId, Document $function, string $projectID, Database $database): Document
|
||||
{
|
||||
global $runtimes;
|
||||
global $orchestration;
|
||||
@@ -284,22 +310,22 @@ function runBuildStage(string $tagID, Document $function, string $projectID, Dat
|
||||
$buildStdout = '';
|
||||
$buildStderr = '';
|
||||
|
||||
// Check if tag is already built
|
||||
$tag = Authorization::skip(function () use ($tagID, $database) {
|
||||
return $database->getDocument('tags', $tagID);
|
||||
// Check if build has already been run
|
||||
$build = Authorization::skip(function () use ($buildId, $database) {
|
||||
return $database->getDocument('builds', $buildId);
|
||||
});
|
||||
|
||||
try {
|
||||
// If we already have a built package ready there is no need to rebuild.
|
||||
if ($tag->getAttribute('status') === 'ready' && \file_exists($tag->getAttribute('buildPath'))) {
|
||||
return $tag;
|
||||
if ($build->getAttribute('status') === 'ready' && \file_exists($build->getAttribute('outputPath'))) {
|
||||
return $build;
|
||||
}
|
||||
|
||||
// Update Tag Status
|
||||
$tag->setAttribute('status', 'building');
|
||||
$build->setAttribute('status', 'building');
|
||||
|
||||
Authorization::skip(function () use ($tag, $database) {
|
||||
$database->updateDocument('tags', $tag->getId(), $tag);
|
||||
Authorization::skip(function () use ($build, $database) {
|
||||
$database->updateDocument('builds', $build->getId(), $build);
|
||||
});
|
||||
|
||||
// Check if runtime is active
|
||||
@@ -307,26 +333,22 @@ function runBuildStage(string $tagID, Document $function, string $projectID, Dat
|
||||
? $runtimes[$function->getAttribute('runtime', '')]
|
||||
: null;
|
||||
|
||||
if ($tag->getAttribute('functionId') !== $function->getId()) {
|
||||
throw new Exception('Tag not found', 404);
|
||||
}
|
||||
|
||||
if (\is_null($runtime)) {
|
||||
throw new Exception('Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
|
||||
}
|
||||
|
||||
// Grab Tag Files
|
||||
$tagPath = $tag->getAttribute('path', '');
|
||||
$tagPathTarget = '/tmp/project-' . $projectID . '/' . $tag->getId() . '/code.tar.gz';
|
||||
$tagPath = $build->getAttribute('source', '');
|
||||
$sourceType = $build->getAttribute('sourceType', '');
|
||||
|
||||
$device = Storage::getDevice('functions');
|
||||
|
||||
$tagPathTarget = '/tmp/project-' . $projectID . '/' . $build->getId() . '/code.tar.gz';
|
||||
$tagPathTargetDir = \pathinfo($tagPathTarget, PATHINFO_DIRNAME);
|
||||
|
||||
$container = 'build-stage-' . $tag->getId();
|
||||
$container = 'build-stage-' . $build->getId();
|
||||
|
||||
// Perform various checks
|
||||
if (!\is_readable($tagPath)) {
|
||||
throw new Exception('Code is not readable: ' . $tag->getAttribute('path', ''));
|
||||
}
|
||||
|
||||
if (!\file_exists($tagPathTargetDir)) {
|
||||
if (!\mkdir($tagPathTargetDir, 0755, true)) {
|
||||
throw new Exception('Can\'t create directory ' . $tagPathTargetDir);
|
||||
@@ -334,22 +356,31 @@ function runBuildStage(string $tagID, Document $function, string $projectID, Dat
|
||||
}
|
||||
|
||||
if (!\file_exists($tagPathTarget)) {
|
||||
if (!\copy($tagPath, $tagPathTarget)) {
|
||||
throw new Exception('Can\'t create temporary code file ' . $tagPathTarget);
|
||||
if (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) === Storage::DEVICE_LOCAL) {
|
||||
if (!\copy($tagPath, $tagPathTarget)) {
|
||||
throw new Exception('Can\'t create temporary code file ' . $tagPathTarget);
|
||||
}
|
||||
} else {
|
||||
$buffer = $device->read($tagPath);
|
||||
\file_put_contents($tagPathTarget, $buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$device->exists($tagPath)) {
|
||||
throw new Exception('Code is not readable: ' . $build->getAttribute('source', ''));
|
||||
}
|
||||
|
||||
// Set build container's environment variables
|
||||
$vars = \array_merge($function->getAttribute('vars', []), [
|
||||
'APPWRITE_FUNCTION_ID' => $function->getId(),
|
||||
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name', ''),
|
||||
'APPWRITE_FUNCTION_TAG' => $tag->getId(),
|
||||
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'],
|
||||
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'],
|
||||
'APPWRITE_FUNCTION_PROJECT_ID' => $projectID,
|
||||
'APPWRITE_ENTRYPOINT_NAME' => $tag->getAttribute('entrypoint')
|
||||
]);
|
||||
|
||||
$vars = \array_merge($vars, $build->getAttribute('envVars', []));
|
||||
|
||||
// Start tracking time
|
||||
$buildStart = \microtime(true);
|
||||
$buildTime = \time();
|
||||
@@ -362,9 +393,9 @@ function runBuildStage(string $tagID, Document $function, string $projectID, Dat
|
||||
$value = strval($value);
|
||||
}
|
||||
|
||||
if (!\file_exists('/tmp/project-' . $projectID . '/' . $tag->getId() . '/builtCode')) {
|
||||
if (!\mkdir('/tmp/project-' . $projectID . '/' . $tag->getId() . '/builtCode', 0755, true)) {
|
||||
throw new Exception('Can\'t create directory /tmp/project-' . $projectID . '/' . $tag->getId() . '/builtCode');
|
||||
if (!\file_exists('/tmp/project-' . $projectID . '/' . $build->getId() . '/builtCode')) {
|
||||
if (!\mkdir('/tmp/project-' . $projectID . '/' . $build->getId() . '/builtCode', 0755, true)) {
|
||||
throw new Exception('Can\'t create directory /tmp/project-' . $projectID . '/' . $build->getId() . '/builtCode');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -379,7 +410,7 @@ function runBuildStage(string $tagID, Document $function, string $projectID, Dat
|
||||
'appwrite-created' => strval($buildTime),
|
||||
'appwrite-runtime' => $function->getAttribute('runtime', ''),
|
||||
'appwrite-project' => $projectID,
|
||||
'appwrite-tag' => $tagID
|
||||
'appwrite-build' => $build->getId(),
|
||||
],
|
||||
command: [
|
||||
'tail',
|
||||
@@ -389,7 +420,7 @@ function runBuildStage(string $tagID, Document $function, string $projectID, Dat
|
||||
hostname: $container,
|
||||
mountFolder: $tagPathTargetDir,
|
||||
volumes: [
|
||||
'/tmp/project-' . $projectID . '/' . $tag->getId() . '/builtCode' . ':/usr/builtCode:rw'
|
||||
'/tmp/project-' . $projectID . '/' . $build->getId() . '/builtCode' . ':/usr/builtCode:rw'
|
||||
]
|
||||
);
|
||||
|
||||
@@ -417,26 +448,6 @@ function runBuildStage(string $tagID, Document $function, string $projectID, Dat
|
||||
throw new Exception('Failed to extract tar: ' . $untarStderr);
|
||||
}
|
||||
|
||||
$entrypointStdout = '';
|
||||
$entrypointStderr = '';
|
||||
|
||||
// Check if entrypoint file exists
|
||||
// $entrypointTest = $orchestration->execute(
|
||||
// name: $container,
|
||||
// command: [
|
||||
// 'tail',
|
||||
// '-f',
|
||||
// '/usr/code/'.$tag->getAttribute('entrypoint')
|
||||
// ],
|
||||
// stdout: $entrypointStdout,
|
||||
// stderr: $entrypointStderr,
|
||||
// timeout: 60
|
||||
// );
|
||||
|
||||
// if ($entrypointStdout === '') {
|
||||
// throw new Exception('Entrypoint file not found: ' . $tag->getAttribute('entrypoint'));
|
||||
// }
|
||||
|
||||
// Build Code / Install Dependencies
|
||||
$buildSuccess = $orchestration->execute(
|
||||
name: $container,
|
||||
@@ -454,7 +465,7 @@ function runBuildStage(string $tagID, Document $function, string $projectID, Dat
|
||||
$compressStdout = '';
|
||||
$compressStderr = '';
|
||||
|
||||
$builtCodePath = '/tmp/project-' . $projectID . '/' . $tag->getId() . '/builtCode/code.tar.gz';
|
||||
$builtCodePath = '/tmp/project-' . $projectID . '/' . $build->getId() . '/builtCode/code.tar.gz';
|
||||
|
||||
$compressSuccess = $orchestration->execute(
|
||||
name: $container,
|
||||
@@ -489,36 +500,43 @@ function runBuildStage(string $tagID, Document $function, string $projectID, Dat
|
||||
}
|
||||
}
|
||||
|
||||
if (!\rename($builtCodePath, $path)) {
|
||||
throw new Exception('Failed moving file', 500);
|
||||
if (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) === Storage::DEVICE_LOCAL) {
|
||||
if (!$device->move($builtCodePath, $path)) {
|
||||
throw new Exception('Failed to upload built code upload to storage', 500);
|
||||
}
|
||||
} else {
|
||||
if (!$device->upload($builtCodePath, $path)) {
|
||||
throw new Exception('Failed to upload built code upload to storage', 500);
|
||||
}
|
||||
}
|
||||
|
||||
if ($buildStdout == '') {
|
||||
$buildStdout = 'Build Successful!';
|
||||
}
|
||||
|
||||
$tag->setAttribute('buildPath', $path)
|
||||
$build->setAttribute('outputPath', $path)
|
||||
->setAttribute('status', 'ready')
|
||||
->setAttribute('buildStdout', \utf8_encode(\mb_substr($buildStdout, -4096)))
|
||||
->setAttribute('buildStderr', \utf8_encode(\mb_substr($buildStderr, -4096)));
|
||||
->setAttribute('stdout', \utf8_encode(\mb_substr($buildStdout, -4096)))
|
||||
->setAttribute('stderr', \utf8_encode(\mb_substr($buildStderr, -4096)))
|
||||
->setAttribute('buildTime', $buildTime);
|
||||
|
||||
// Update tag with built code attribute
|
||||
$tag = Authorization::skip(function () use ($tag, $tagID, $database) {
|
||||
return $database->updateDocument('tags', $tagID, $tag);
|
||||
// Update build with built code attribute
|
||||
$build = Authorization::skip(function () use ($build, $buildId, $database) {
|
||||
return $database->updateDocument('builds', $buildId, $build);
|
||||
});
|
||||
|
||||
$buildEnd = \microtime(true);
|
||||
|
||||
Console::info('Tag Built in ' . ($buildEnd - $buildStart) . ' seconds');
|
||||
Console::info('Build Stage Ran in ' . ($buildEnd - $buildStart) . ' seconds');
|
||||
} catch (Exception $e) {
|
||||
Console::error('Tag build failed: ' . $e->getMessage());
|
||||
Console::error('Build failed: ' . $e->getMessage());
|
||||
|
||||
$tag->setAttribute('status', 'failed')
|
||||
->setAttribute('buildStdout', \utf8_encode(\mb_substr($buildStdout, -4096)))
|
||||
->setAttribute('buildStderr', \utf8_encode(\mb_substr($e->getMessage(), -4096)));
|
||||
$build->setAttribute('status', 'failed')
|
||||
->setAttribute('stdout', \utf8_encode(\mb_substr($buildStdout, -4096)))
|
||||
->setAttribute('stderr', \utf8_encode(\mb_substr($e->getMessage(), -4096)));
|
||||
|
||||
Authorization::skip(function () use ($tag, $tagID, $database) {
|
||||
return $database->updateDocument('tags', $tagID, $tag);
|
||||
$build = Authorization::skip(function () use ($build, $buildId, $database) {
|
||||
return $database->updateDocument('builds', $buildId, $build);
|
||||
});
|
||||
|
||||
// also remove the container if it exists
|
||||
@@ -527,20 +545,29 @@ function runBuildStage(string $tagID, Document $function, string $projectID, Dat
|
||||
}
|
||||
}
|
||||
|
||||
return $tag;
|
||||
return $build;
|
||||
}
|
||||
|
||||
function createRuntimeServer(string $functionId, string $projectId, Document $tag, Database $database): void
|
||||
function createRuntimeServer(string $functionId, string $projectId, string $tagId, Database $database): void
|
||||
{
|
||||
global $orchestration;
|
||||
global $runtimes;
|
||||
global $activeFunctions;
|
||||
|
||||
// Grab Tag Document
|
||||
// Grab Function Document
|
||||
$function = Authorization::skip(function () use ($database, $functionId) {
|
||||
return $database->getDocument('functions', $functionId);
|
||||
});
|
||||
|
||||
$tag = Authorization::skip(function () use ($database, $tagId) {
|
||||
return $database->getDocument('tags', $tagId);
|
||||
});
|
||||
|
||||
// Grab Build Document
|
||||
$build = Authorization::skip(function () use ($database, $tag) {
|
||||
return $database->getDocument('builds', $tag->getAttribute('buildId'));
|
||||
});
|
||||
|
||||
// Check if function isn't already created
|
||||
$functions = $orchestration->list(['label' => 'appwrite-type=function', 'name' => 'appwrite-function-' . $tag->getId()]);
|
||||
|
||||
@@ -572,9 +599,11 @@ function createRuntimeServer(string $functionId, string $projectId, Document $ta
|
||||
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'],
|
||||
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'],
|
||||
'APPWRITE_FUNCTION_PROJECT_ID' => $projectId,
|
||||
'APPWRITE_INTERNAL_RUNTIME_KEY' => $secret,
|
||||
'APPWRITE_INTERNAL_RUNTIME_KEY' => $secret
|
||||
]);
|
||||
|
||||
$vars = \array_merge($vars, $build->getAttribute('envVars', [])); // for gettng endpoint.
|
||||
|
||||
$container = 'appwrite-function-' . $tag->getId();
|
||||
|
||||
if ($activeFunctions->exists($container) && !(\substr($activeFunctions->get($container)['status'], 0, 2) === 'Up')) { // Remove container if not online
|
||||
@@ -589,25 +618,23 @@ function createRuntimeServer(string $functionId, string $projectId, Document $ta
|
||||
}
|
||||
|
||||
// Check if tag hasn't failed
|
||||
if ($tag->getAttribute('status') == 'failed') {
|
||||
if ($build->getAttribute('status') == 'failed') {
|
||||
throw new Exception('Tag build failed, please check your logs.', 500);
|
||||
}
|
||||
|
||||
// Check if tag is built yet.
|
||||
if ($tag->getAttribute('status') !== 'ready') {
|
||||
if ($build->getAttribute('status') !== 'ready') {
|
||||
throw new Exception('Tag is not built yet', 500);
|
||||
}
|
||||
|
||||
// Grab Tag Files
|
||||
$tagPath = $tag->getAttribute('buildPath', '');
|
||||
$tagPath = $build->getAttribute('outputPath', '');
|
||||
|
||||
$tagPathTarget = '/tmp/project-' . $projectId . '/' . $tag->getId() . '/builtCode/code.tar.gz';
|
||||
$tagPathTarget = '/tmp/project-' . $projectId . '/' . $build->getId() . '/builtCode/code.tar.gz';
|
||||
$tagPathTargetDir = \pathinfo($tagPathTarget, PATHINFO_DIRNAME);
|
||||
$container = 'appwrite-function-' . $tag->getId();
|
||||
|
||||
if (!\is_readable($tagPath)) {
|
||||
throw new Exception('Code is not readable: ' . $tagPath);
|
||||
}
|
||||
$device = Storage::getDevice('functions');
|
||||
|
||||
if (!\file_exists($tagPathTargetDir)) {
|
||||
if (!\mkdir($tagPathTargetDir, 0755, true)) {
|
||||
@@ -616,10 +643,19 @@ function createRuntimeServer(string $functionId, string $projectId, Document $ta
|
||||
}
|
||||
|
||||
if (!\file_exists($tagPathTarget)) {
|
||||
if (!\copy($tagPath, $tagPathTarget)) {
|
||||
throw new Exception('Can\'t create temporary code file ' . $tagPathTarget);
|
||||
if (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) === Storage::DEVICE_LOCAL) {
|
||||
if (!\copy($tagPath, $tagPathTarget)) {
|
||||
throw new Exception('Can\'t create temporary code file ' . $tagPathTarget);
|
||||
}
|
||||
} else {
|
||||
$buffer = $device->read($tagPath);
|
||||
\file_put_contents($tagPathTarget, $buffer);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// if ($device->exists($tagPathTarget)) {
|
||||
// throw new Exception('Code is not readable: ' . $tagPathTarget);
|
||||
// };
|
||||
|
||||
/**
|
||||
* Limit CPU Usage - DONE
|
||||
@@ -697,6 +733,11 @@ function execute(string $trigger, string $projectId, string $executionId, string
|
||||
return $database->getDocument('tags', $function->getAttribute('tag', ''));
|
||||
});
|
||||
|
||||
// Grab Build Document
|
||||
$build = Authorization::skip(function () use ($database, $tag) {
|
||||
return $database->getDocument('builds', $tag->getAttribute('buildId', ''));
|
||||
});
|
||||
|
||||
if ($tag->getAttribute('functionId') !== $function->getId()) {
|
||||
throw new Exception('Tag not found', 404);
|
||||
}
|
||||
@@ -726,7 +767,7 @@ function execute(string $trigger, string $projectId, string $executionId, string
|
||||
}
|
||||
|
||||
|
||||
if ($tag->getAttribute('status') == 'building') {
|
||||
if ($build->getAttribute('status') == 'building') {
|
||||
Console::error('Execution Failed. Reason: Code was still being built.');
|
||||
|
||||
$execution->setAttribute('status', 'failed')
|
||||
@@ -765,11 +806,38 @@ function execute(string $trigger, string $projectId, string $executionId, string
|
||||
'APPWRITE_FUNCTION_PROJECT_ID' => $projectId,
|
||||
]);
|
||||
|
||||
$vars = \array_merge($vars, $build->getAttribute('envVars', []));
|
||||
|
||||
$container = 'appwrite-function-' . $tag->getId();
|
||||
|
||||
try {
|
||||
if ($tag->getAttribute('status') !== 'ready') {
|
||||
runBuildStage($tag->getId(), $function, $projectId, $database);
|
||||
if ($build->getAttribute('status') !== 'ready') {
|
||||
// Create a new build entry
|
||||
$buildId = $database->getId();
|
||||
Authorization::skip(function () use ($buildId, $database, $tag, $userId) {
|
||||
$database->createDocument('builds', new Document([
|
||||
'$id' => $buildId,
|
||||
'$read' => (!$userId == '') ? ['user:' . $userId] : [],
|
||||
'$write' => [],
|
||||
'dateCreated' => time(),
|
||||
'status' => 'pending',
|
||||
'outputPath' => '',
|
||||
'source' => $tag->getAttribute('path'),
|
||||
'sourceType' => Storage::DEVICE_LOCAL,
|
||||
'stdout' => '',
|
||||
'stderr' => '',
|
||||
'buildTime' => 0,
|
||||
'envVars' => [
|
||||
'APPWRITE_ENTRYPOINT_NAME' => $tag->getAttribute('entrypoint')
|
||||
]
|
||||
]));
|
||||
|
||||
$tag->setAttribute('buildId', $buildId);
|
||||
|
||||
$database->updateDocument('tags', $tag->getId(), $tag);
|
||||
});
|
||||
|
||||
runBuildStage($buildId, $function, $projectId, $database);
|
||||
sleep(1);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
@@ -786,7 +854,7 @@ function execute(string $trigger, string $projectId, string $executionId, string
|
||||
|
||||
try {
|
||||
if (!$activeFunctions->exists($container)) { // Create contianer if not ready
|
||||
createRuntimeServer($functionId, $projectId, $tag, $database);
|
||||
createRuntimeServer($functionId, $projectId, $tag->getId(), $database);
|
||||
} else if ($activeFunctions->get($container)['status'] === 'Down') {
|
||||
sleep(1);
|
||||
} else {
|
||||
@@ -829,6 +897,8 @@ function execute(string $trigger, string $projectId, string $executionId, string
|
||||
'APPWRITE_FUNCTION_PROJECT_ID' => $projectId
|
||||
]);
|
||||
|
||||
$vars = \array_merge($vars, $build->getAttribute('envVars', []));
|
||||
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
|
||||
@@ -850,7 +920,7 @@ function execute(string $trigger, string $projectId, string $executionId, string
|
||||
|
||||
$body = \json_encode([
|
||||
'path' => '/usr/code',
|
||||
'file' => $tag->getAttribute('entrypoint', ''),
|
||||
'file' => $build->getAttribute('envVars', [])['APPWRITE_ENTRYPOINT_NAME'],
|
||||
'env' => $vars,
|
||||
'payload' => $data,
|
||||
'timeout' => $function->getAttribute('timeout', (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900))
|
||||
|
||||
@@ -129,6 +129,9 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
|
||||
<span data-ls-if="{{tag.status}} == 'processing' || {{tag.status}} == 'building' || {{tag.status}} == 'pending'" style="color: var(--config-color-info)" class="pull-start" data-ls-bind="{{tag.status}}"></span>
|
||||
<span class="pull-start" data-ls-bind=" | Created {{tag.dateCreated|timeSince}} | {{tag.size|humanFileSize}}{{tag.size|humanFileUnit}}"></span>
|
||||
|
||||
<span data-ls-if="{{tag.status}} == 'failed'"> | <button type="button" class="link text-size-small" data-ls-ui-trigger="open-stderr-{{tag.$id}}">Logs</button></span>
|
||||
<span data-ls-if="{{tag.status}} == 'ready'"> | <button type="button" class="link text-size-small" data-ls-ui-trigger="open-stdout-{{tag.$id}}">Logs</button></span>
|
||||
|
||||
<form data-ls-if="{{tag.$id}} !== {{project-function.tag}}" name="functions.deleteTag" class="pull-start"
|
||||
data-analytics
|
||||
data-analytics-activity
|
||||
@@ -149,9 +152,6 @@ $usageStatsEnabled = $this->getParam('usageStatsEnabled', true);
|
||||
|
||||
| <button type="submit" class="link text-danger text-size-small">Delete</button>
|
||||
</form>
|
||||
|
||||
<span data-ls-if="{{tag.status}} == 'failed'"> | <button type="button" class="link text-size-small" data-ls-ui-trigger="open-stderr-{{tag.$id}}">Open Error</button></span>
|
||||
<span data-ls-if="{{tag.status}} == 'ready'"> | <button type="button" class="link text-size-small" data-ls-ui-trigger="open-stdout-{{tag.$id}}">Open Build Logs</button></span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Get a list of all the current user build logs. You can use the query params to filter your results. On admin mode, this endpoint will return a list of all of the project's executions. [Learn more about different API modes](/docs/admin).
|
||||
@@ -31,6 +31,7 @@ use Appwrite\Utopia\Response\Model\Error;
|
||||
use Appwrite\Utopia\Response\Model\ErrorDev;
|
||||
use Appwrite\Utopia\Response\Model\Execution;
|
||||
use Appwrite\Utopia\Response\Model\SyncExecution;
|
||||
use Appwrite\Utopia\Response\Model\Build;
|
||||
use Appwrite\Utopia\Response\Model\File;
|
||||
use Appwrite\Utopia\Response\Model\Func;
|
||||
use Appwrite\Utopia\Response\Model\Index;
|
||||
@@ -146,6 +147,8 @@ class Response extends SwooleResponse
|
||||
const MODEL_EXECUTION = 'execution';
|
||||
const MODEL_SYNC_EXECUTION = 'syncExecution';
|
||||
const MODEL_EXECUTION_LIST = 'executionList';
|
||||
const MODEL_BUILD = 'build';
|
||||
const MODEL_BUILD_LIST = 'buildList';
|
||||
|
||||
// Project
|
||||
const MODEL_PROJECT = 'project';
|
||||
@@ -203,6 +206,7 @@ class Response extends SwooleResponse
|
||||
->setModel(new BaseList('Functions List', self::MODEL_FUNCTION_LIST, 'functions', self::MODEL_FUNCTION))
|
||||
->setModel(new BaseList('Tags List', self::MODEL_TAG_LIST, 'tags', self::MODEL_TAG))
|
||||
->setModel(new BaseList('Executions List', self::MODEL_EXECUTION_LIST, 'executions', self::MODEL_EXECUTION))
|
||||
->setModel(new BaseList('Builds List', self::MODEL_BUILD_LIST, 'builds', self::MODEL_BUILD))
|
||||
->setModel(new BaseList('Projects List', self::MODEL_PROJECT_LIST, 'projects', self::MODEL_PROJECT, true, false))
|
||||
->setModel(new BaseList('Webhooks List', self::MODEL_WEBHOOK_LIST, 'webhooks', self::MODEL_WEBHOOK, true, false))
|
||||
->setModel(new BaseList('API Keys List', self::MODEL_KEY_LIST, 'keys', self::MODEL_KEY, true, false))
|
||||
@@ -242,6 +246,7 @@ class Response extends SwooleResponse
|
||||
->setModel(new Tag())
|
||||
->setModel(new Execution())
|
||||
->setModel(new SyncExecution())
|
||||
->setModel(new Build())
|
||||
->setModel(new Project())
|
||||
->setModel(new Webhook())
|
||||
->setModel(new Key())
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class Build extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('$id', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Build ID.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('dateCreated', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'The tag creation date in Unix timestamp.',
|
||||
'default' => 0,
|
||||
'example' => 1592981250,
|
||||
])
|
||||
->addRule('status', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The build status.',
|
||||
'default' => '',
|
||||
'example' => 'ready',
|
||||
])
|
||||
->addRule('stdout', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The stdout of the build.',
|
||||
'default' => '',
|
||||
'example' => '',
|
||||
])
|
||||
->addRule('stderr', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The stderr of the build.',
|
||||
'default' => '',
|
||||
'example' => '',
|
||||
])
|
||||
->addRule('buildTime', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'The build time in seconds.',
|
||||
'default' => 0,
|
||||
'example' => 0,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName():string
|
||||
{
|
||||
return 'Build';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Collection
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType():string
|
||||
{
|
||||
return Response::MODEL_BUILD;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user