Move Build data into it's own collection

Move Build data into it's own collection
This commit is contained in:
Bradley Schofield
2021-12-06 14:12:41 +00:00
parent e64bb9faf5
commit f30169dcc4
7 changed files with 453 additions and 93 deletions
+126
View File
@@ -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',
+89 -2
View File
@@ -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
View File
@@ -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))
+3 -3
View File
@@ -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="&nbsp; | &nbsp; Created {{tag.dateCreated|timeSince}} &nbsp; | &nbsp; {{tag.size|humanFileSize}}{{tag.size|humanFileUnit}}"></span>
<span data-ls-if="{{tag.status}} == 'failed'">&nbsp; | &nbsp;<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'">&nbsp; | &nbsp;<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);
&nbsp; | &nbsp;<button type="submit" class="link text-danger text-size-small">Delete</button>
</form>
<span data-ls-if="{{tag.status}} == 'failed'">&nbsp; | &nbsp;<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'">&nbsp; | &nbsp;<button type="button" class="link text-size-small" data-ls-ui-trigger="open-stdout-{{tag.$id}}">Open Build Logs</button></span>
</div>
</li>
</ul>
+1
View File
@@ -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).
+5
View File
@@ -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;
}
}