diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 4fbe98b0e..96cc18db0 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -367,7 +367,7 @@ App::patch('/v1/functions/:functionId/tag') $function = $dbForProject->getDocument('functions', $functionId); $tag = $dbForProject->getDocument('tags', $tag); - $build = $dbForProject->getDocument('builds', $tag->getAttribute('buildId')); + $build = $dbForProject->getDocument('builds', $tag->getAttribute('buildId', '')); if ($function->isEmpty()) { throw new Exception('Function not found', 404); @@ -433,11 +433,8 @@ App::delete('/v1/functions/:functionId') // Request executor to delete tag containers $ch = \curl_init(); - \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/cleanup/function"); - \curl_setopt($ch, CURLOPT_POST, true); - \curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ - 'functionId' => $functionId - ])); + \curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); + \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/functions/$functionId"); \curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); \curl_setopt($ch, CURLOPT_TIMEOUT, 900); \curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); @@ -743,11 +740,8 @@ App::delete('/v1/functions/:functionId/tags/:tagId') // Request executor to delete tag containers $ch = \curl_init(); - \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/cleanup/tag"); - \curl_setopt($ch, CURLOPT_POST, true); - \curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ - 'tagId' => $tagId - ])); + \curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); + \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/tags/$tagId"); \curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); \curl_setopt($ch, CURLOPT_TIMEOUT, 900); \curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); @@ -901,13 +895,12 @@ App::post('/v1/functions/:functionId/executions') // Directly execute function. $ch = \curl_init(); - \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/execute"); + \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/functions/{$function->getId()}/executions"); \curl_setopt($ch, CURLOPT_POST, true); \curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'trigger' => 'http', 'projectId' => $project->getId(), 'executionId' => $execution->getId(), - 'functionId' => $function->getId(), 'data' => $data, 'webhooks' => $project->getAttribute('webhooks', []), 'userId' => $user->getId(), diff --git a/app/executor.php b/app/executor.php index 4e4a29625..37c72afcb 100644 --- a/app/executor.php +++ b/app/executor.php @@ -312,7 +312,7 @@ function createRuntimeServer(string $functionId, string $projectId, string $tagI 'key' => $secret, ]); - Console::info('Runtime Server created in ' . ($executionEnd - $executionStart) . ' seconds'); + Console::success('Runtime Server created in ' . ($executionEnd - $executionStart) . ' seconds'); } else { Console::info('Runtime server is ready to run'); } @@ -601,7 +601,7 @@ function execute(string $trigger, string $projectId, string $executionId, string $executionTime = ($executionEnd - $executionStart); $functionStatus = ($statusCode >= 200 && $statusCode < 300) ? 'completed' : 'failed'; - Console::info('Function executed in ' . ($executionEnd - $executionStart) . ' seconds, status: ' . $functionStatus); + Console::success('Function executed in ' . ($executionEnd - $executionStart) . ' seconds, status: ' . $functionStatus); $execution->setAttribute('tagId', $tag->getId()) ->setAttribute('status', $functionStatus) @@ -914,7 +914,7 @@ function runBuildStage(string $buildId, string $projectID): Document return $build; } -App::post('/v1/execute') // Define Route +App::post('/v1/functions/:functionId/executions') ->desc('Execute a function') ->param('trigger', '', new Text(1024)) ->param('projectId', '', new Text(1024)) @@ -932,21 +932,17 @@ App::post('/v1/execute') // Define Route function (string $trigger, string $projectId, string $executionId, string $functionId, string $event, string $eventData, string $data, array $webhooks, string $userId, string $jwt, Response $response, Database $dbForProject) { try { $data = execute($trigger, $projectId, $executionId, $functionId, $dbForProject, $event, $eventData, $data, $webhooks, $userId, $jwt); - $response->json($data); + $response + ->setStatusCode(Response::STATUS_CODE_OK) + ->send(); } catch (Exception $e) { logError($e, 'executeEndpoint'); - - $response - ->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate') - ->addHeader('Expires', '0') - ->addHeader('Pragma', 'no-cache') - ->json(['error' => $e->getMessage()]); + throw $e; } } ); -// Cleanup Endpoints used internally by appwrite when a function or tag gets deleted to also clean up their containers -App::post('/v1/cleanup/function') +App::delete('/v1/functions/:functionId') ->param('functionId', '', new UID()) ->inject('response') ->inject('dbForProject') @@ -1003,7 +999,39 @@ App::post('/v1/cleanup/function') } ); -App::post('/v1/cleanup/tag') +App::post('/v1/functions/:functionId/tags/:tagId/runtime') + ->desc('Create a new runtime server for a tag') + ->param('functionId', '', new UID(), 'Function unique ID.') + ->param('tagId', '', new UID(), 'Tag unique ID.') + ->inject('response') + ->inject('dbForProject') + ->inject('projectID') + ->action(function (string $functionId, string $tagId, Response $response, Database $dbForProject, string $projectID) use ($runtimes) { + // Get function document + $function = $dbForProject->getDocument('functions', $functionId); + 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); + } + + $runtime = $runtimes[$function->getAttribute('runtime')] ?? null; + if (\is_null($runtime)) { + throw new Exception('Runtime "' . $function->getAttribute('runtime', '') . '" not found.', 404); + } + + createRuntimeServer($functionId, $projectID, $tagId, $dbForProject); + + $response + ->setStatusCode(201) + ->send(); + }); + +App::delete('/v1/tags/:tagId') ->param('tagId', '', new UID(), 'Tag unique ID.') ->inject('response') ->inject('dbForProject') @@ -1049,130 +1077,7 @@ App::post('/v1/cleanup/tag') return $response->json(['success' => true]); }); -App::post('/v1/tag') - ->desc('Create a new build') - ->param('functionId', '', new UID(), 'Function unique ID.') - ->param('tagId', '', new UID(), 'Tag unique ID.') - ->param('userId', '', new UID(), 'User unique ID.', true) - ->inject('response') - ->inject('dbForProject') - ->inject('projectID') - ->inject('register') - ->action(function (string $functionId, string $tagId, string $userId, Response $response, Database $dbForProject, string $projectID, Registry $register) use ($runtimes) { - // Get function document - $function = $dbForProject->getDocument('functions', $functionId); - // Get tag document - $tag = $dbForProject->getDocument('tags', $tagId); - - // Check if both documents exist - if ($function->isEmpty()) { - throw new Exception('Function not found', 404); - } - - if ($tag->isEmpty()) { - throw new Exception('Tag not found', 404); - } - - $runtime = $runtimes[$function->getAttribute('runtime')] ?? null; - - if (\is_null($runtime)) { - throw new Exception('Runtime "' . $function->getAttribute('runtime', '') . '" is not supported'); - } - - // Create a new build entry - $buildId = $dbForProject->getId(); - - if ($tag->getAttribute('buildId')) { - $buildId = $tag->getAttribute('buildId'); - } else { - try { - $dbForProject->createDocument('builds', new Document([ - '$id' => $buildId, - '$read' => (!empty($userId)) ? ['user:' . $userId] : [], - '$write' => ['role:all'], - 'dateCreated' => time(), - 'status' => 'processing', - 'runtime' => $function->getAttribute('runtime'), - 'outputPath' => '', - 'source' => $tag->getAttribute('path'), - 'sourceType' => Storage::DEVICE_LOCAL, - 'stdout' => '', - 'stderr' => '', - 'time' => 0, - 'vars' => [ - '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); - - $dbForProject->updateDocument('tags', $tag->getId(), $tag); - } catch (\Throwable $th) { - var_dump($tag->getArrayCopy()); - throw $th; - } - } - - // Build Code - go(function () use ($projectID, $tagId, $buildId, $functionId, $function, $register) { - try { - $db = $register->get('dbPool')->get(); - $redis = $register->get('redisPool')->get(); - $cache = new Cache(new RedisCache($redis)); - - $dbForProject = new Database(new MariaDB($db), $cache); - $dbForProject->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite')); - $dbForProject->setNamespace('_project_' . $projectID); - // Build Code - runBuildStage($buildId, $projectID); - - // 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 tag - $tag = $dbForProject->getDocument('tags', $tagId); - - // Grab build - $build = $dbForProject->getDocument('builds', $buildId); - - // If the build failed, it won't be possible to deploy - if ($build->getAttribute('status') !== 'ready') { - return; - } - - 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); - $function = $dbForProject->updateDocument('functions', $function->getId(), $function); - } - - // Deploy Runtime Server - createRuntimeServer($functionId, $projectID, $tagId, $dbForProject); - } catch (\Throwable $th) { - } finally { - $register->get('dbPool')->put($db); - $register->get('redisPool')->put($redis); - } - }); - - if (false === $function) { - throw new Exception('Failed saving function to DB', 500); - } - - $response->dynamic($function, Response::MODEL_FUNCTION); - }); - -// Build Endpoints -App::post('/v1/build/:buildId') // Start a Build +App::post('/v1/builds/:buildId') ->desc('Start a build') ->param('buildId', '', new UID(), 'Build unique ID.', false) ->inject('response') @@ -1197,11 +1102,8 @@ App::post('/v1/build/:buildId') // Start a Build if ($build->getAttribute('status') === 'finished') { throw new Exception('Build is already finished', 409); } - - go(function () use ($buildId, $dbForProject, $projectID) { - // Build Code - runBuildStage($buildId, $projectID, $dbForProject); - }); + + runBuildStage($buildId, $projectID, $dbForProject); $response ->setStatusCode(Response::STATUS_CODE_CREATED) diff --git a/app/workers/builds.php b/app/workers/builds.php index d97ef0d4b..99901fe04 100644 --- a/app/workers/builds.php +++ b/app/workers/builds.php @@ -21,14 +21,12 @@ Console::success(APP_NAME.' build worker v1 has started'); class BuildsV1 extends Worker { - public function getName(): string { + public function getName(): string + { return "builds"; } - public function init(): void - { - Console::success("Initializing..."); - } + public function init(): void {} public function run(): void { @@ -59,7 +57,7 @@ class BuildsV1 extends Worker { // TODO: What is a reasonable time to wait for a build to complete? $ch = \curl_init(); - \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/build/$buildId"); + \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/builds/$buildId"); \curl_setopt($ch, CURLOPT_POST, true); \curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); \curl_setopt($ch, CURLOPT_TIMEOUT, 900); @@ -88,7 +86,7 @@ class BuildsV1 extends Worker protected function triggerCreateRuntimeServer(string $projectId, string $functionId, string $tagId) { $ch = \curl_init(); - \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/executor/runtime"); + \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/functions/$functionId/tags/$tagId/runtime"); \curl_setopt($ch, CURLOPT_POST, true); \curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); \curl_setopt($ch, CURLOPT_TIMEOUT, 900); @@ -98,10 +96,6 @@ class BuildsV1 extends Worker 'x-appwrite-project: '.$projectId, 'x-appwrite-executor-key: '. App::getEnv('_APP_EXECUTOR_SECRET', '') ]); - \curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ - 'functionId' => $functionId, - 'tagId' => $tagId - ])); $response = \curl_exec($ch); $responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); @@ -161,8 +155,8 @@ class BuildsV1 extends Worker 'sourceType' => Storage::DEVICE_LOCAL, 'stdout' => '', 'stderr' => '', - 'buildTime' => 0, - 'envVars' => [ + 'time' => 0, + 'vars' => [ 'ENTRYPOINT_NAME' => $tag->getAttribute('entrypoint'), 'APPWRITE_FUNCTION_ID' => $function->getId(), 'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name', ''), @@ -232,11 +226,8 @@ class BuildsV1 extends Worker return; } - Console::success("Runtime Server created"); + Console::success("[ SUCCESS ] Runtime Server created"); } - public function shutdown(): void - { - Console::success("Shutting Down..."); - } + public function shutdown(): void {} } diff --git a/app/workers/functions.php b/app/workers/functions.php index a96a1fa7f..5dc23c9cc 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -224,13 +224,12 @@ class FunctionsV1 extends Worker public function execute(string $trigger, string $projectId, string $executionId, Database $database, Document $function, string $event = '', string $eventData = '', string $data = '', array $webhooks = [], string $userId = '', string $jwt = ''): void { $ch = \curl_init(); - \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/execute"); + \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/functions/{$function->getId()}/executions"); \curl_setopt($ch, CURLOPT_POST, true); \curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'trigger' => $trigger, 'projectId' => $projectId, 'executionId' => $executionId, - 'functionId' => $function->getId(), 'event' => $event, 'eventData' => $eventData, 'data' => $data, diff --git a/src/Appwrite/Resque/Worker.php b/src/Appwrite/Resque/Worker.php index 1444c9066..f222d6651 100644 --- a/src/Appwrite/Resque/Worker.php +++ b/src/Appwrite/Resque/Worker.php @@ -45,7 +45,7 @@ abstract class Worker * @throws \Exception|\Throwable */ public function init() { - throw new Exception("Please implement getName method in worker"); + throw new Exception("Please implement init method in worker"); } /** @@ -56,7 +56,7 @@ abstract class Worker * @throws \Exception|\Throwable */ public function run() { - throw new Exception("Please implement getName method in worker"); + throw new Exception("Please implement run method in worker"); } /** @@ -67,7 +67,7 @@ abstract class Worker * @throws \Exception|\Throwable */ public function shutdown() { - throw new Exception("Please implement getName method in worker"); + throw new Exception("Please implement shutdown method in worker"); } const MAX_ATTEMPTS = 10;