1
0
Fork 0
mirror of synced 2024-06-01 18:39:57 +12:00
appwrite/app/workers/builds.php

234 lines
8.5 KiB
PHP
Raw Normal View History

2022-01-24 11:25:46 +13:00
<?php
use Appwrite\Resque\Worker;
use Cron\CronExpression;
use Utopia\Database\Validator\Authorization;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Storage\Storage;
use Utopia\Database\Document;
use Utopia\Config\Config;
require_once __DIR__.'/../init.php';
2022-01-25 09:06:29 +13:00
// Disable Auth since we already validate it in the API
Authorization::disable();
2022-01-24 11:25:46 +13:00
Console::title('Builds V1 Worker');
Console::success(APP_NAME.' build worker v1 has started');
2022-01-25 09:06:29 +13:00
// TODO: Executor should return appropriate response codes.
2022-01-24 11:25:46 +13:00
class BuildsV1 extends Worker
{
2022-01-24 11:25:46 +13:00
2022-01-26 12:45:41 +13:00
public function getName(): string
{
2022-01-24 11:25:46 +13:00
return "builds";
}
2022-01-26 12:45:41 +13:00
public function init(): void {}
2022-01-24 11:25:46 +13:00
public function run(): void
{
$type = $this->args['type'] ?? '';
$projectId = $this->args['projectId'] ?? '';
switch ($type) {
case BUILD_TYPE_TAG:
2022-01-24 11:25:46 +13:00
$functionId = $this->args['functionId'] ?? '';
$tagId = $this->args['tagId'] ?? '';
2022-01-25 09:06:29 +13:00
Console::info("[ INFO ] Creating build for tag: $tagId");
2022-01-24 11:25:46 +13:00
$this->buildTag($projectId, $functionId, $tagId);
break;
case BUILD_TYPE_RETRY:
$buildId = $this->args['buildId'] ?? '';
2022-01-25 09:06:29 +13:00
Console::info("[ INFO ] Retrying build for id: $buildId");
$this->triggerBuild($projectId, $buildId);
break;
2022-01-24 11:25:46 +13:00
default:
throw new \Exception('Invalid build type');
2022-01-24 11:25:46 +13:00
break;
}
}
2022-01-25 09:06:29 +13:00
protected function triggerBuild(string $projectId, string $buildId)
2022-01-24 11:25:46 +13:00
{
// TODO: What is a reasonable time to wait for a build to complete?
$ch = \curl_init();
2022-01-26 12:45:41 +13:00
\curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/builds/$buildId");
2022-01-24 11:25:46 +13:00
\curl_setopt($ch, CURLOPT_POST, true);
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
\curl_setopt($ch, CURLOPT_TIMEOUT, 900);
\curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
\curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'x-appwrite-project: '.$projectId,
'x-appwrite-executor-key: '. App::getEnv('_APP_EXECUTOR_SECRET', '')
]);
$response = \curl_exec($ch);
$responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = \curl_error($ch);
if (!empty($error)) {
throw new \Exception($error);
}
\curl_close($ch);
2022-01-26 07:44:21 +13:00
if ($responseStatus >= 400) {
2022-01-24 11:25:46 +13:00
throw new \Exception("Build failed with status code: $responseStatus");
}
}
protected function triggerCreateRuntimeServer(string $projectId, string $functionId, string $tagId)
{
$ch = \curl_init();
2022-01-26 12:45:41 +13:00
\curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/functions/$functionId/tags/$tagId/runtime");
2022-01-24 11:25:46 +13:00
\curl_setopt($ch, CURLOPT_POST, true);
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
\curl_setopt($ch, CURLOPT_TIMEOUT, 900);
\curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
\curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'x-appwrite-project: '.$projectId,
'x-appwrite-executor-key: '. App::getEnv('_APP_EXECUTOR_SECRET', '')
]);
$response = \curl_exec($ch);
$responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = \curl_error($ch);
if (!empty($error)) {
throw new \Exception($error);
}
\curl_close($ch);
2022-01-25 09:06:29 +13:00
if ($responseStatus >= 400) {
2022-01-24 11:25:46 +13:00
throw new \Exception("Build failed with status code: $responseStatus");
}
}
protected function buildTag(string $projectId, string $functionId, string $tagId)
{
$dbForProject = $this->getProjectDB($projectId);
2022-01-25 09:06:29 +13:00
$function = $dbForProject->getDocument('functions', $functionId);
2022-01-24 11:25:46 +13:00
if ($function->isEmpty()) {
throw new Exception('Function not found', 404);
}
// Get tag document
$tag = $dbForProject->getDocument('tags', $tagId);
if ($tag->isEmpty()) {
throw new Exception('Tag not found', 404);
}
$runtimes = Config::getParam('runtimes', []);
$key = $function->getAttribute('runtime');
$runtime = isset($runtimes[$key]) ? $runtimes[$key] : null;
if (\is_null($runtime)) {
throw new Exception('Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
}
$buildId = $tag->getAttribute('buildId', '');
// If build ID is empty, create a new build
if (empty($buildId)) {
try {
$buildId = $dbForProject->getId();
// TODO : There is no way to associate a build with a tag. So we need to add a tagId attribute to the build document
// TODO : What should be the read and write permissions for a build ?
$dbForProject->createDocument('builds', new Document([
'$id' => $buildId,
2022-01-25 09:06:29 +13:00
'$read' => [],
'$write' => [],
2022-01-24 11:25:46 +13:00
'dateCreated' => time(),
'status' => 'processing',
'runtime' => $function->getAttribute('runtime'),
'outputPath' => '',
'source' => $tag->getAttribute('path'),
'sourceType' => Storage::DEVICE_LOCAL,
'stdout' => '',
'stderr' => '',
2022-01-26 12:45:41 +13:00
'time' => 0,
'vars' => [
2022-01-24 11:25:46 +13:00
'ENTRYPOINT_NAME' => $tag->getAttribute('entrypoint'),
'APPWRITE_FUNCTION_ID' => $function->getId(),
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name', ''),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'],
'APPWRITE_FUNCTION_PROJECT_ID' => $projectId,
]
]));
$tag->setAttribute('buildId', $buildId);
$tag = $dbForProject->updateDocument('tags', $tagId, $tag);
} catch (\Throwable $th) {
Console::error($th->getMessage());
$tag->setAttribute('status', 'failed');
$tag->setAttribute('buildId', '');
$tag = $dbForProject->updateDocument('tags', $tagId, $tag);
return;
}
}
// Build the Code
try {
2022-01-25 09:06:29 +13:00
Console::info("[ INFO ] Creating build with id: $buildId");
2022-01-24 11:25:46 +13:00
$tag->setAttribute('status', 'building');
$tag = $dbForProject->updateDocument('tags', $tagId, $tag);
2022-01-25 09:06:29 +13:00
$this->triggerBuild($projectId, $buildId);
2022-01-24 11:25:46 +13:00
} catch (\Throwable $th) {
Console::error($th->getMessage());
$tag->setAttribute('status', 'failed');
$tag = $dbForProject->updateDocument('tags', $tagId, $tag);
return;
}
2022-01-25 09:06:29 +13:00
Console::success("[ SUCCESS ] Build id: $buildId completed");
2022-01-24 11:25:46 +13:00
// Update the schedule
$schedule = $function->getAttribute('schedule', '');
$cron = (empty($function->getAttribute('tag')) && !empty($schedule)) ? new CronExpression($schedule) : null;
$next = (empty($function->getAttribute('tag')) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : 0;
// Grab build
$build = $dbForProject->getDocument('builds', $buildId);
// If the build failed, it won't be possible to deploy
if ($build->getAttribute('status') !== 'ready') {
throw new Exception('Build failed', 500);
}
if ($tag->getAttribute('automaticDeploy') === true) {
// Update the function document setting the tag as the active one
$function
->setAttribute('tag', $tag->getId())
->setAttribute('scheduleNext', (int)$next);
2022-01-25 09:06:29 +13:00
$function = $dbForProject->updateDocument('functions', $functionId, $function);
2022-01-24 11:25:46 +13:00
}
// Deploy Runtime Server
try {
2022-01-25 09:06:29 +13:00
Console::info("[ INFO ] Creating runtime server");
$this->triggerCreateRuntimeServer($projectId, $functionId, $tagId, $dbForProject);
2022-01-24 11:25:46 +13:00
} catch (\Throwable $th) {
Console::error($th->getMessage());
$tag->setAttribute('status', 'failed');
$tag = $dbForProject->updateDocument('tags', $tagId, $tag);
return;
}
2022-01-26 12:45:41 +13:00
Console::success("[ SUCCESS ] Runtime Server created");
2022-01-24 11:25:46 +13:00
}
2022-01-26 12:45:41 +13:00
public function shutdown(): void {}
2022-01-24 11:25:46 +13:00
}