diff --git a/app/config/collections.php b/app/config/collections.php index 15471c8c77..7614cde4a7 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -2158,9 +2158,9 @@ $collections = [ ], 'indexes' => [ [ - '$id' => '_key_status', + '$id' => '_key_deployment', 'type' => Database::INDEX_KEY, - 'attributes' => ['status'], + 'attributes' => ['deploymentId'], 'lengths' => [Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC], ] diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 3c1da0b370..fdb075daee 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -423,44 +423,13 @@ App::delete('/v1/functions/:functionId') ->inject('response') ->inject('dbForProject') ->inject('deletes') - ->inject('project') - ->action(function ($functionId, $response, $dbForProject, $deletes, $project) { + ->action(function ($functionId, $response, $dbForProject, $deletes) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $deletes */ - /** @var Utopia\Database\Document $project */ $function = $dbForProject->getDocument('functions', $functionId); - // Request executor to delete deployment containers - $ch = \curl_init(); - \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); - \curl_setopt($ch, CURLOPT_HTTPHEADER, [ - 'Content-Type: application/json', - 'x-appwrite-project: '.$project->getId(), - 'x-appwrite-executor-key: '. App::getEnv('_APP_EXECUTOR_SECRET', '') - ]); - - $executorResponse = \curl_exec($ch); - - $error = \curl_error($ch); - - if (!empty($error)) { - throw new Exception('Executor Cleanup Error: ' . $error, 500); - } - - // Check status code - $statusCode = \curl_getinfo($ch, CURLINFO_HTTP_CODE); - if (200 !== $statusCode) { - throw new Exception('Executor error: ' . $executorResponse, $statusCode); - } - - \curl_close($ch); - if ($function->isEmpty()) { throw new Exception('Function not found', 404); } @@ -713,64 +682,29 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId') ->inject('response') ->inject('dbForProject') ->inject('usage') - ->inject('project') - ->action(function ($functionId, $deploymentId, $response, $dbForProject, $usage, $project) { + ->inject('deletes') + ->action(function ($functionId, $deploymentId, $response, $dbForProject, $usage, $deletes) { /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Database\Database $dbForProject */ /** @var Appwrite\Event\Event $usage */ - /** @var Utopia\Database\Document $project */ + /** @var Appwrite\Event\Event $deletes */ $function = $dbForProject->getDocument('functions', $functionId); - if ($function->isEmpty()) { throw new Exception('Function not found', 404); } $deployment = $dbForProject->getDocument('deployments', $deploymentId); + if ($deployment->isEmpty()) { + throw new Exception('Deployment not found', 404); + } if ($deployment->getAttribute('resourceId') !== $function->getId()) { throw new Exception('Deployment not found', 404); } - if ($deployment->isEmpty()) { - throw new Exception('deployment not found', 404); - } - - // Request executor to delete deployment containers - $ch = \curl_init(); - \curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); - \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/deployments/$deploymentId"); - \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: '.$project->getId(), - 'x-appwrite-executor-key: '. App::getEnv('_APP_EXECUTOR_SECRET', '') - ]); - - $executorResponse = \curl_exec($ch); - - $error = \curl_error($ch); - - if (!empty($error)) { - throw new Exception('Executor Cleanup error: ' . $error, 500); - } - - // Check status code - $statusCode = \curl_getinfo($ch, CURLINFO_HTTP_CODE); - if (200 !== $statusCode) { - throw new Exception('Executor error: ' . $executorResponse, $statusCode); - } - - \curl_close($ch); - - $device = Storage::getDevice('functions'); - - if ($device->delete($deployment->getAttribute('path', ''))) { - if (!$dbForProject->deleteDocument('deployments', $deployment->getId())) { - throw new Exception('Failed to remove deployment from DB', 500); - } + if (!$dbForProject->deleteDocument('deployments', $deployment->getId())) { + throw new Exception('Failed to remove deployment from DB', 500); } if($function->getAttribute('deployment') === $deployment->getId()) { // Reset function deployment @@ -783,6 +717,11 @@ App::delete('/v1/functions/:functionId/deployments/:deploymentId') ->setParam('storage', $deployment->getAttribute('size', 0) * -1) ; + $deletes + ->setParam('type', DELETE_TYPE_DOCUMENT) + ->setParam('document', $deployment) + ; + $response->noContent(); }); diff --git a/app/executor.php b/app/executor.php index 19f8946087..34a4d93455 100644 --- a/app/executor.php +++ b/app/executor.php @@ -408,7 +408,7 @@ function execute(string $trigger, string $projectId, string $executionId, string $buildId = $database->getId(); $database->createDocument('builds', new Document([ '$id' => $buildId, - '$read' => ($userId !== '') ? ['user:' . $userId] : [], + '$read' => [], '$write' => [], 'startTime' => time(), 'deploymentId' => $deployment->getId(), @@ -685,9 +685,7 @@ function runBuildStage(string $buildId, string $deploymentId, string $projectID) // Update deployment Status $build->setAttribute('status', 'building'); - $deployment->setAttribute('status', 'building'); $database->updateDocument('builds', $buildId, $build); - $database->updateDocument('deployments', $deploymentId, $deployment); // Check if runtime is active $runtime = $runtimes[$build->getAttribute('runtime', '')] ?? null; @@ -748,6 +746,7 @@ function runBuildStage(string $buildId, string $deploymentId, string $projectID) } $vars = $resource->getAttribute('vars', []); + $vars['ENTRYPOINT_NAME'] = $resource->getAttribute('entrypoint', ''); $orchestration ->setCpus(App::getEnv('_APP_FUNCTIONS_CPUS', 0)) @@ -903,14 +902,11 @@ function runBuildStage(string $buildId, string $deploymentId, string $projectID) ->setAttribute('stdout', \utf8_encode(\mb_substr($buildStdout, -4096))) ->setAttribute('stderr', \utf8_encode(\mb_substr($e->getMessage(), -4096))) ->setAttribute('startTime', $buildStart) - ->setAttribute('endTime', \microtime(true)) - ->setAttribute('duration', \microtime(true) - $buildStart); + ->setAttribute('endTime', \time()) + ->setAttribute('duration', \time() - $buildStart); $build = $database->updateDocument('builds', $buildId, $build); - $deployment->setAttribute('status', 'failed'); - $database->updateDocument('deployments', $deploymentId, $deployment); - // also remove the container if it exists if (isset($id)) { $orchestration->remove($id, true); @@ -955,58 +951,63 @@ App::post('/v1/functions/:functionId/executions') App::delete('/v1/functions/:functionId') ->desc('Delete a function') - ->param('functionId', '', new UID(), 'The FunctionID to delete') + ->param('functionId', '', new UID()) + ->inject('projectId') ->inject('response') ->inject('dbForProject') ->action( - function (string $functionId, Response $response, Database $dbForProject) use ($orchestrationPool) { - try { - /** @var Orchestration $orchestration */ - $orchestration = $orchestrationPool->get(); - - // Get function document - $function = $dbForProject->getDocument('functions', $functionId); + function (string $functionId, string $projectId, Response $response, Database $dbForProject) use ($orchestrationPool) { - // Check if function exists - if ($function->isEmpty()) { - throw new Exception('Function not found', 404); - } - - $results = $dbForProject->find('deployments', [new Query('functionId', Query::TYPE_EQUAL, [$functionId])], 999); - - // If amount is 0 then we simply return true - if (count($results) === 0) { - $response - ->setStatusCode(Response::STATUS_CODE_OK) - ->send(); - } - - // Delete the containers of all deployments - foreach ($results as $deployment) { - // Remove any ongoing builds - if ($deployment->getAttribute('buildId')) { - $build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId')); - - if ($build->getAttribute('status') === 'building') { - // Remove the build - $orchestration->remove('build-stage-' . $deployment->getAttribute('buildId'), true); - Console::info('Removed build for deployment ' . $deployment['$id']); - } - } - - $orchestration->remove('appwrite-function-' . $deployment['$id'], true); - Console::info('Removed container for deployment ' . $deployment['$id']); - } + $results = $dbForProject->find('deployments', [new Query('resourceId', Query::TYPE_EQUAL, [$functionId])], 999); + // If amount is 0 then we simply return true + if (count($results) === 0) { $response ->setStatusCode(Response::STATUS_CODE_OK) ->send(); - } catch (Throwable $th) { - $orchestrationPool->put($orchestration); - throw $th; - } finally { - $orchestrationPool->put($orchestration); } + + Console::info('Deleting function: ' . $functionId); + // Delete the containers of all deployments + global $register; + foreach ($results as $deployment) { + go(function () use ($orchestrationPool, $deployment, $register, $projectId) { + $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); + + try { + $orchestration = $orchestrationPool->get(); + // Remove the container of the deployment + $orchestration->remove('appwrite-function-' . $deployment['$id'], true); + Console::success('Removed container for deployment: ' . $deployment['$id']); + + $builds = $dbForProject->find('builds', [ + new Query('deploymentId', Query::TYPE_EQUAL, [$deployment['$id']]), + new Query('status', Query::TYPE_EQUAL, ['building']) + ], 999); + + // Remove all the build containers + foreach ($builds as $build) { + $orchestration->remove('build-stage-' . $build['$id'], true); + Console::success("Removed build contanier: $build for deployment: " . $deployment['$id']); + } + } catch (\Throwable $th) { + Console::error($th->getMessage()); + } finally { + $orchestrationPool->put($orchestration); + $register->get('dbPool')->put($db); + $register->get('redisPool')->put($redis); + } + }); + } + + $response + ->setStatusCode(Response::STATUS_CODE_OK) + ->send(); } ); @@ -1045,40 +1046,43 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/runtime') App::delete('/v1/deployments/:deploymentId') ->desc('Delete a deployment') ->param('deploymentId', '', new UID(), 'Deployment unique ID.') + ->inject('projectId') ->inject('response') - ->inject('dbForProject') - ->action(function (string $deploymentId, Response $response, Database $dbForProject) use ($orchestrationPool) { - try { - /** @var Orchestration $orchestration */ - $orchestration = $orchestrationPool->get(); - - // Get deployment document - $deployment = $dbForProject->getDocument('deployments', $deploymentId); + ->action(function (string $deploymentId, string $projectId, Response $response) use ($orchestrationPool) { + Console::info('Deleting deployment: ' . $deploymentId); + global $register; + go(function () use ($projectId, $orchestrationPool, $register, $deploymentId) { + try { + $orchestration = $orchestrationPool->get(); + // Remove the container of the deployment + $orchestration->remove('appwrite-function-' . $deploymentId , true); + Console::success('Removed container for deployment: ' . $deploymentId); - // Check if deployment exists - if ($deployment->isEmpty()) { - throw new Exception('Deployment not found', 404); - } + $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); - // Remove any ongoing builds - if ($deployment->getAttribute('buildId')) { - $build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId')); + $builds = $dbForProject->find('builds', [ + new Query('deploymentId', Query::TYPE_EQUAL, [$deploymentId]), + new Query('status', Query::TYPE_EQUAL, ['building']) + ], 999); - if ($build->getAttribute('status') === 'building') { - // Remove the build - $orchestration->remove('build-stage-' . $deployment->getAttribute('buildId'), true); - Console::info('Removed build for deployment ' . $deployment['$id']); + // Remove all the build containers + foreach ($builds as $build) { + $orchestration->remove('build-stage-' . $build['$id'], true); + Console::success("Removed build container: $build for deployment: " . $deploymentId); } + } catch (\Throwable $th) { + Console::error($th->getMessage()); + } finally { + $orchestrationPool->put($orchestration); + $register->get('dbPool')->put($db); + $register->get('redisPool')->put($redis); } - - // Remove the container of the deployment - $orchestration->remove('appwrite-function-' . $deployment['$id'], true); - Console::info('Removed container for deployment ' . $deployment['$id']); - } catch (Throwable $th) { - $orchestrationPool->put($orchestration); - throw $th; - } - $orchestrationPool->put($orchestration); + }); $response ->setStatusCode(Response::STATUS_CODE_OK) @@ -1147,12 +1151,10 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId') // Deploy Runtime Server try { - Console::info("[ INFO ] Creating runtime server"); + Console::info("Creating runtime server"); createRuntimeServer($functionId, $projectId, $deploymentId, $dbForProject); } catch (\Throwable $th) { Console::error($th->getMessage()); - $deployment->setAttribute('status', 'failed'); - $deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment); throw $th; } }); diff --git a/app/init.php b/app/init.php index fe330815e8..3c22aa3aef 100644 --- a/app/init.php +++ b/app/init.php @@ -100,6 +100,7 @@ const DELETE_TYPE_DOCUMENT = 'document'; const DELETE_TYPE_COLLECTIONS = 'collections'; const DELETE_TYPE_PROJECTS = 'projects'; const DELETE_TYPE_FUNCTIONS = 'functions'; +const DELETE_TYPE_DEPLOYMENTS = 'deployments'; const DELETE_TYPE_USERS = 'users'; const DELETE_TYPE_TEAMS= 'teams'; const DELETE_TYPE_EXECUTIONS = 'executions'; diff --git a/app/workers/builds.php b/app/workers/builds.php index 21236c2da3..acb8ed6633 100644 --- a/app/workers/builds.php +++ b/app/workers/builds.php @@ -37,7 +37,7 @@ class BuildsV1 extends Worker case BUILD_TYPE_DEPLOYMENT: $functionId = $this->args['functionId'] ?? ''; $deploymentId = $this->args['deploymentId'] ?? ''; - Console::info("[ INFO ] Creating build for deployment: $deploymentId"); + Console::info("Creating build for deployment: $deploymentId"); $this->buildDeployment($projectId, $functionId, $deploymentId); break; @@ -45,7 +45,7 @@ class BuildsV1 extends Worker $buildId = $this->args['buildId'] ?? ''; $functionId = $this->args['functionId'] ?? ''; $deploymentId = $this->args['deploymentId'] ?? ''; - Console::info("[ INFO ] Retrying build for id: $buildId"); + Console::info("Retrying build for id: $buildId"); $this->createBuild($projectId, $functionId, $deploymentId, $buildId); break; @@ -113,28 +113,21 @@ class BuildsV1 extends Worker if (empty($buildId)) { try { $buildId = $dbForProject->getId(); - // TODO : There is no way to associate a build with a deployment. So we need to add a deploymentId attribute to the build document $dbForProject->createDocument('builds', new Document([ '$id' => $buildId, '$read' => [], '$write' => [], - 'dateCreated' => time(), + 'startTime' => time(), + 'deploymentId' => $deploymentId, 'status' => 'processing', - 'runtime' => $function->getAttribute('runtime'), 'outputPath' => '', + 'runtime' => $function->getAttribute('runtime'), 'source' => $deployment->getAttribute('path'), 'sourceType' => Storage::DEVICE_LOCAL, 'stdout' => '', 'stderr' => '', - 'time' => 0, - 'vars' => [ - 'ENTRYPOINT_NAME' => $deployment->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, - ] + 'endTime' => 0, + 'duration' => 0 ])); } catch (\Throwable $th) { $deployment->setAttribute('buildId', ''); @@ -154,7 +147,7 @@ class BuildsV1 extends Worker throw $th; } - Console::success("[ SUCCESS ] Build id: $buildId started"); + Console::success("Build id: $buildId started"); } public function shutdown(): void {} diff --git a/app/workers/deletes.php b/app/workers/deletes.php index bc7721d550..0449ed0d91 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -8,11 +8,14 @@ use Appwrite\Resque\Worker; use Utopia\Storage\Device\Local; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\TimeLimit; +use Utopia\App; use Utopia\CLI\Console; use Utopia\Audit\Audit; require_once __DIR__ . '/../init.php'; +Authorization::disable(); + Console::title('Deletes V1 Worker'); Console::success(APP_NAME . ' deletes worker v1 has started' . "\n"); @@ -50,6 +53,9 @@ class DeletesV1 extends Worker case DELETE_TYPE_FUNCTIONS: $this->deleteFunction($document, $projectId); break; + case DELETE_TYPE_DEPLOYMENTS: + $this->deleteDeployment($document, $projectId); + break; case DELETE_TYPE_USERS: $this->deleteUser($document, $projectId); break; @@ -202,7 +208,7 @@ class DeletesV1 extends Worker ], $this->getProjectDB($projectId)); $user->setAttribute('sessions', []); - $updated = Authorization::skip(fn() => $this->getProjectDB($projectId)->updateDocument('users', $userId, $user)); + $updated = $this->getProjectDB($projectId)->updateDocument('users', $userId, $user); // Delete Memberships and decrement team membership counts $this->deleteByGroup('memberships', [ @@ -307,24 +313,139 @@ class DeletesV1 extends Worker protected function deleteFunction(Document $document, string $projectId): void { $dbForProject = $this->getProjectDB($projectId); - $device = new Local(APP_STORAGE_FUNCTIONS . '/app-' . $projectId); - // Delete Deployments + /** + * Request executor to delete all deployment containers + */ + try { + $ch = \curl_init(); + \curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); + \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/functions/{$document->getId()}"); + \curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + \curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'x-appwrite-project: '. $projectId, + 'x-appwrite-executor-key: '. App::getEnv('_APP_EXECUTOR_SECRET', '') + ]); + + $executorResponse = \curl_exec($ch); + $error = \curl_error($ch); + if (!empty($error)) { + throw new Exception($error, 500); + } + + $statusCode = \curl_getinfo($ch, CURLINFO_HTTP_CODE); + if ($statusCode >= 400) { + throw new Exception('Executor error: ' . $executorResponse, $statusCode); + } + + \curl_close($ch); + } catch (Throwable $th) { + Console::error($th->getMessage()); + } + + /** + * Delete Deployments + */ + $storageFunctions = new Local(APP_STORAGE_FUNCTIONS . '/app-' . $projectId); + $deploymentIds = []; $this->deleteByGroup('deployments', [ - new Query('functionId', Query::TYPE_EQUAL, [$document->getId()]) - ], $dbForProject, function (Document $document) use ($device) { - - if ($device->delete($document->getAttribute('path', ''))) { - Console::success('Delete code deployment: ' . $document->getAttribute('path', '')); + new Query('resourceId', Query::TYPE_EQUAL, [$document->getId()]) + ], $dbForProject, function (Document $document) use ($storageFunctions, &$deploymentIds) { + $deploymentIds[] = $document->getId(); + if ($storageFunctions->delete($document->getAttribute('path', ''), true)) { + Console::success('Deleted deployment files: ' . $document->getAttribute('path', '')); } else { - Console::error('Failed to delete code deployment: ' . $document->getAttribute('path', '')); + Console::error('Failed to delete deployment files: ' . $document->getAttribute('path', '')); } }); + /** + * Delete builds + */ + if (!empty($deploymentIds)) { + $storageBuilds = new Local(APP_STORAGE_BUILDS . '/app-' . $projectId); + $this->deleteByGroup('builds', [ + new Query('deploymentId', Query::TYPE_EQUAL, $deploymentIds) + ], $dbForProject, function (Document $document) use ($storageBuilds) { + if ($storageBuilds->delete($document->getAttribute('outputPath', ''), true)) { + Console::success('Deleted build files: ' . $document->getAttribute('outputPath', '')); + } else { + Console::error('Failed to delete build files: ' . $document->getAttribute('outputPath', '')); + } + }); + } + // Delete Executions $this->deleteByGroup('executions', [ new Query('functionId', Query::TYPE_EQUAL, [$document->getId()]) ], $dbForProject); + + } + + /** + * @param Document $document deployment document + * @param string $projectId + */ + protected function deleteDeployment(Document $document, string $projectId): void + { + $dbForProject = $this->getProjectDB($projectId); + + /** + * Request executor to delete the deployment containers + */ + try { + $ch = \curl_init(); + \curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); + // TODO: Implement coroutines. + \curl_setopt($ch, CURLOPT_URL, "http://appwrite-executor/v1/deployments/{$document->getId()}"); + \curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + \curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'x-appwrite-project: '. $projectId, + 'x-appwrite-executor-key: '. App::getEnv('_APP_EXECUTOR_SECRET', '') + ]); + + $executorResponse = \curl_exec($ch); + $error = \curl_error($ch); + if (!empty($error)) { + throw new Exception($error, 500); + } + + $statusCode = \curl_getinfo($ch, CURLINFO_HTTP_CODE); + if ($statusCode >= 400) { + throw new Exception('Executor error: ' . $executorResponse, $statusCode); + } + + \curl_close($ch); + } catch (Throwable $th) { + Console::error($th->getMessage()); + } + + /** + * Delete deployment files + */ + $storageFunctions = new Local(APP_STORAGE_FUNCTIONS . '/app-' . $projectId); + if ($storageFunctions->delete($document->getAttribute('path', ''), true)) { + Console::success('Deleted deployment files: ' . $document->getAttribute('path', '')); + } else { + Console::error('Failed to delete deployment files: ' . $document->getAttribute('path', '')); + } + + /** + * Delete builds + */ + $storageBuilds = new Local(APP_STORAGE_BUILDS . '/app-' . $projectId); + $this->deleteByGroup('builds', [ + new Query('deploymentId', Query::TYPE_EQUAL, [$document->getId()]) + ], $dbForProject, function (Document $document) use ($storageBuilds) { + if ($storageBuilds->delete($document->getAttribute('outputPath', ''), true)) { + Console::success('Deleted build files: ' . $document->getAttribute('outputPath', '')); + } else { + Console::error('Failed to delete build files: ' . $document->getAttribute('outputPath', '')); + } + }); + } @@ -337,8 +458,6 @@ class DeletesV1 extends Worker */ protected function deleteById(Document $document, Database $database, callable $callback = null): bool { - Authorization::disable(); - if ($database->deleteDocument($document->getCollection(), $document->getId())) { Console::success('Deleted document "' . $document->getId() . '" successfully'); @@ -351,8 +470,6 @@ class DeletesV1 extends Worker Console::error('Failed to delete document: ' . $document->getId()); return false; } - - Authorization::reset(); } /** @@ -369,9 +486,7 @@ class DeletesV1 extends Worker $executionStart = \microtime(true); while ($sum === $limit) { - Authorization::disable(); $projects = $this->getConsoleDB()->find('projects', [], $limit, ($chunk * $limit)); - Authorization::reset(); $chunk++; @@ -410,12 +525,8 @@ class DeletesV1 extends Worker while ($sum === $limit) { $chunk++; - Authorization::disable(); - $results = $database->find($collection, $queries, $limit, 0); - Authorization::reset(); - $sum = count($results); Console::info('Deleting chunk #' . $chunk . '. Found ' . $sum . ' documents'); diff --git a/docker-compose.yml b/docker-compose.yml index f649d9c2ab..a73b34a7b5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -238,6 +238,7 @@ services: - appwrite-uploads:/storage/uploads:rw - appwrite-cache:/storage/cache:rw - appwrite-functions:/storage/functions:rw + - appwrite-builds:/storage/builds:rw - appwrite-certificates:/storage/certificates:rw - ./app:/usr/src/code/app - ./src:/usr/src/code/src @@ -396,6 +397,7 @@ services: volumes: - /var/run/docker.sock:/var/run/docker.sock - appwrite-functions:/storage/functions:rw + - appwrite-builds:/storage/builds:rw - /tmp:/tmp:rw - ./app:/usr/src/code/app - ./src:/usr/src/code/src @@ -696,6 +698,7 @@ volumes: appwrite-uploads: appwrite-certificates: appwrite-functions: + appwrite-builds: appwrite-influxdb: appwrite-config: appwrite-executor: diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index a175bb48a9..3255caf2a9 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -87,7 +87,7 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(201, $deployment['headers']['status-code']); // Wait for deployment to be built. - sleep(5); + sleep(10); $function = $this->client->call(Client::METHOD_PATCH, '/functions/'.$function['body']['$id'].'/deployment', [ 'content-type' => 'application/json', @@ -97,6 +97,8 @@ class FunctionsCustomClientTest extends Scope 'deployment' => $deploymentId, ]); + // var_dump($function); + $this->assertEquals(200, $function['headers']['status-code']); $execution = $this->client->call(Client::METHOD_POST, '/functions/'.$function['body']['$id'].'/executions', [ @@ -117,14 +119,14 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(201, $execution['headers']['status-code']); - $execution = $this->client->call(Client::METHOD_POST, '/functions/'.$function['body']['$id'].'/executions', array_merge([ + // Cleanup : Delete function + $response = $this->client->call(Client::METHOD_DELETE, '/functions/'.$function['body']['$id'], [ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'async' => true, - ]); + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], []); - $this->assertEquals(401, $execution['headers']['status-code']); + $this->assertEquals(204, $response['headers']['status-code']); return []; } @@ -300,6 +302,16 @@ class FunctionsCustomClientTest extends Scope 'cursor' => $base['body']['executions'][1]['$id'], 'cursorDirection' => Database::CURSOR_BEFORE ]); + + // Cleanup : Delete function + $response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], []); + + $this->assertEquals(204, $response['headers']['status-code']); + } public function testSynchronousExecution():array @@ -346,7 +358,7 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(201, $deployment['headers']['status-code']); // Wait for deployment to be built. - sleep(5); + sleep(10); $function = $this->client->call(Client::METHOD_PATCH, '/functions/'.$functionId.'/deployment', [ 'content-type' => 'application/json', @@ -382,6 +394,15 @@ class FunctionsCustomClientTest extends Scope $this->assertNotEmpty($output['APPWRITE_FUNCTION_JWT']); $this->assertEquals($projectId, $output['APPWRITE_FUNCTION_PROJECT_ID']); + // Cleanup : Delete function + $response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], []); + + $this->assertEquals(204, $response['headers']['status-code']); + return []; } } diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index ce220806bb..9bb2a296e9 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -291,10 +291,9 @@ class FunctionsCustomServerTest extends Scope $this->assertNotEmpty($deployment['body']['$id']); $this->assertIsInt($deployment['body']['dateCreated']); $this->assertEquals('index.php', $deployment['body']['entrypoint']); - // $this->assertGreaterThan(10000, $deployment['body']['size']); // Wait for deployment to build. - sleep(5); + sleep(15); /** * Test for FAILURE @@ -450,7 +449,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals('', $execution['body']['stderr']); $this->assertEquals(0, $execution['body']['time']); - sleep(15); + sleep(5); $execution = $this->client->call(Client::METHOD_GET, '/functions/'.$data['functionId'].'/executions/'.$executionId, array_merge([ 'content-type' => 'application/json', @@ -628,7 +627,7 @@ class FunctionsCustomServerTest extends Scope /** * Test for SUCCESS */ - $function = $this->client->call(Client::METHOD_DELETE, '/functions/'.$data['functionId'], array_merge([ + $function = $this->client->call(Client::METHOD_DELETE, '/functions/'. $data['functionId'], array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); @@ -686,7 +685,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(201, $deployment['headers']['status-code']); // Allow build step to run - sleep(5); + sleep(10); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/'.$functionId.'/deployment', array_merge([ 'content-type' => 'application/json', @@ -728,6 +727,15 @@ class FunctionsCustomServerTest extends Scope $this->assertLessThan(3, $executions['body']['executions'][0]['time']); $this->assertEquals($executions['body']['executions'][0]['stdout'], ''); $this->assertEquals($executions['body']['executions'][0]['stderr'], 'Execution timed out.'); + + // Cleanup : Delete function + $response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], []); + + $this->assertEquals(204, $response['headers']['status-code']); } /** @@ -769,7 +777,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(201, $deployment['headers']['status-code']); // Allow build step to run - sleep(5); + sleep(10); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/'.$functionId.'/deployment', array_merge([ 'content-type' => 'application/json', @@ -829,6 +837,15 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals($executions['body']['executions'][0]['$id'], $executionId); $this->assertEquals($executions['body']['executions'][0]['trigger'], 'http'); $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['stdout']); + + // Cleanup : Delete function + $response = $this->client->call(Client::METHOD_DELETE, '/functions/'. $functionId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], []); + + $this->assertEquals(204, $response['headers']['status-code']); } public function testGetRuntimes() diff --git a/tests/unit/Docker/ComposeTest.php b/tests/unit/Docker/ComposeTest.php index 79fbdd9f95..c484fc17e8 100644 --- a/tests/unit/Docker/ComposeTest.php +++ b/tests/unit/Docker/ComposeTest.php @@ -36,7 +36,7 @@ class ComposeTest extends TestCase public function testServices() { - $this->assertCount(16, $this->object->getServices()); + $this->assertCount(17, $this->object->getServices()); $this->assertEquals('appwrite-telegraf', $this->object->getService('telegraf')->getContainerName()); $this->assertEquals('appwrite', $this->object->getService('appwrite')->getContainerName()); $this->assertEquals('', $this->object->getService('appwrite')->getImageVersion());