diff --git a/app/config/errors.php b/app/config/errors.php index 05409cf8cb..66178bcf7d 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -541,6 +541,11 @@ return [ 'description' => 'Build with the requested ID is already in progress. Please wait before you can retry.', 'code' => 400, ], + Exception::BUILD_ALREADY_COMPLETED => [ + 'name' => Exception::BUILD_ALREADY_COMPLETED, + 'description' => 'Build with the requested ID is already completed and cannot be canceled.', + 'code' => 400, + ], /** Deployments */ Exception::DEPLOYMENT_NOT_FOUND => [ diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 392cf034f9..068fc1c836 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -1444,7 +1444,6 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/build') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'createBuild') - ->label('sdk.description', '/docs/references/functions/create-build.md') ->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT) ->label('sdk.response.model', Response::MODEL_NONE) ->param('functionId', '', new UID(), 'Function ID.') @@ -1462,7 +1461,6 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/build') if ($function->isEmpty()) { throw new Exception(Exception::FUNCTION_NOT_FOUND); } - $deployment = $dbForProject->getDocument('deployments', $deploymentId); if ($deployment->isEmpty()) { @@ -1493,6 +1491,86 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/build') $response->noContent(); }); +App::patch('/v1/functions/:functionId/deployments/:deploymentId/build') + ->groups(['api', 'functions']) + ->desc('Cancel deployment') + ->label('scope', 'functions.write') + ->label('audits.event', 'deployment.update') + ->label('audits.resource', 'function/{request.functionId}') + ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'functions') + ->label('sdk.method', 'updateDeploymentBuild') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_BUILD) + ->param('functionId', '', new UID(), 'Function ID.') + ->param('deploymentId', '', new UID(), 'Deployment ID.') + ->inject('response') + ->inject('dbForProject') + ->inject('project') + ->inject('queueForEvents') + ->action(function (string $functionId, string $deploymentId, Response $response, Database $dbForProject, Document $project, Event $queueForEvents) { + $function = $dbForProject->getDocument('functions', $functionId); + + if ($function->isEmpty()) { + throw new Exception(Exception::FUNCTION_NOT_FOUND); + } + + $deployment = $dbForProject->getDocument('deployments', $deploymentId); + + if ($deployment->isEmpty()) { + throw new Exception(Exception::DEPLOYMENT_NOT_FOUND); + } + + $build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''))); + + if ($build->isEmpty()) { + $buildId = ID::unique(); + $build = $dbForProject->createDocument('builds', new Document([ + '$id' => $buildId, + '$permissions' => [], + 'startTime' => DateTime::now(), + 'deploymentInternalId' => $deployment->getInternalId(), + 'deploymentId' => $deployment->getId(), + 'status' => 'canceled', + 'path' => '', + 'runtime' => $function->getAttribute('runtime'), + 'source' => $deployment->getAttribute('path', ''), + 'sourceType' => '', + 'logs' => '', + 'duration' => 0, + 'size' => 0 + ])); + + $deployment->setAttribute('buildId', $build->getId()); + $deployment->setAttribute('buildInternalId', $build->getInternalId()); + $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment); + } else { + if (\in_array($build->getAttribute('status'), ['ready', 'failed'])) { + throw new Exception(Exception::BUILD_ALREADY_COMPLETED); + } + + $startTime = new \DateTime($build->getAttribute('startTime')); + $endTime = new \DateTime('now'); + $duration = $endTime->getTimestamp() - $startTime->getTimestamp(); + + $build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttributes([ + 'endTime' => DateTime::now(), + 'duration' => $duration, + 'status' => 'canceled' + ])); + } + + $executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST')); + $deleteBuild = $executor->deleteRuntime($project->getId(), $deploymentId . "-build"); + + $queueForEvents + ->setParam('functionId', $function->getId()) + ->setParam('deploymentId', $deployment->getId()); + + $response->dynamic($build, Response::MODEL_BUILD); + }); + App::post('/v1/functions/:functionId/executions') ->groups(['api', 'functions']) ->desc('Create execution') diff --git a/composer.lock b/composer.lock index 7c660bf176..5c30d5a1cc 100644 --- a/composer.lock +++ b/composer.lock @@ -822,16 +822,16 @@ }, { "name": "paragonie/constant_time_encoding", - "version": "v2.6.3", + "version": "v2.7.0", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "58c3f47f650c94ec05a151692652a868995d2938" + "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938", - "reference": "58c3f47f650c94ec05a151692652a868995d2938", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/52a0d99e69f56b9ec27ace92ba56897fe6993105", + "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105", "shasum": "" }, "require": { @@ -885,7 +885,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2022-06-14T06:56:20+00:00" + "time": "2024-05-08T12:18:48+00:00" }, { "name": "phpmailer/phpmailer", @@ -1672,16 +1672,16 @@ }, { "name": "utopia-php/dsn", - "version": "0.2.0", + "version": "0.2.1", "source": { "type": "git", "url": "https://github.com/utopia-php/dsn.git", - "reference": "c11f37a12c3f6aaf9fea97ca7cb363dcc93668d7" + "reference": "42ee37a3d1785100b2f69091c9d4affadb6846eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/dsn/zipball/c11f37a12c3f6aaf9fea97ca7cb363dcc93668d7", - "reference": "c11f37a12c3f6aaf9fea97ca7cb363dcc93668d7", + "url": "https://api.github.com/repos/utopia-php/dsn/zipball/42ee37a3d1785100b2f69091c9d4affadb6846eb", + "reference": "42ee37a3d1785100b2f69091c9d4affadb6846eb", "shasum": "" }, "require": { @@ -1713,9 +1713,9 @@ ], "support": { "issues": "https://github.com/utopia-php/dsn/issues", - "source": "https://github.com/utopia-php/dsn/tree/0.2.0" + "source": "https://github.com/utopia-php/dsn/tree/0.2.1" }, - "time": "2023-11-02T12:01:43+00:00" + "time": "2024-05-07T02:01:25+00:00" }, { "name": "utopia-php/framework", @@ -1966,22 +1966,21 @@ }, { "name": "utopia-php/migration", - "version": "0.4.1", + "version": "0.4.4", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "ae3cfe93f6d313105d226aeb68806660c806a925" + "reference": "a8a5d392bebf082faf289f4dfe09d9fd76844c33" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/ae3cfe93f6d313105d226aeb68806660c806a925", - "reference": "ae3cfe93f6d313105d226aeb68806660c806a925", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/a8a5d392bebf082faf289f4dfe09d9fd76844c33", + "reference": "a8a5d392bebf082faf289f4dfe09d9fd76844c33", "shasum": "" }, "require": { "appwrite/appwrite": "10.1.0", - "php": "8.*", - "utopia-php/cli": "0.*" + "php": "8.*" }, "require-dev": { "laravel/pint": "1.*", @@ -2008,9 +2007,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.4.1" + "source": "https://github.com/utopia-php/migration/tree/0.4.4" }, - "time": "2024-05-01T13:19:18+00:00" + "time": "2024-05-17T05:25:31+00:00" }, { "name": "utopia-php/mongo", @@ -2124,16 +2123,16 @@ }, { "name": "utopia-php/platform", - "version": "0.5.1", + "version": "0.5.2", "source": { "type": "git", "url": "https://github.com/utopia-php/platform.git", - "reference": "3eceef0b6593fe0f7d2efd36d40402a395a4c285" + "reference": "b9feabc79b92dc2b05683a986ad43bce5c1583e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/platform/zipball/3eceef0b6593fe0f7d2efd36d40402a395a4c285", - "reference": "3eceef0b6593fe0f7d2efd36d40402a395a4c285", + "url": "https://api.github.com/repos/utopia-php/platform/zipball/b9feabc79b92dc2b05683a986ad43bce5c1583e3", + "reference": "b9feabc79b92dc2b05683a986ad43bce5c1583e3", "shasum": "" }, "require": { @@ -2141,7 +2140,7 @@ "ext-redis": "*", "php": ">=8.0", "utopia-php/cli": "0.15.*", - "utopia-php/framework": "0.*.*" + "utopia-php/framework": "0.33.*" }, "require-dev": { "laravel/pint": "1.2.*", @@ -2167,9 +2166,9 @@ ], "support": { "issues": "https://github.com/utopia-php/platform/issues", - "source": "https://github.com/utopia-php/platform/tree/0.5.1" + "source": "https://github.com/utopia-php/platform/tree/0.5.2" }, - "time": "2023-12-26T16:14:41+00:00" + "time": "2024-05-22T12:50:35+00:00" }, { "name": "utopia-php/pools", @@ -2499,16 +2498,16 @@ }, { "name": "utopia-php/vcs", - "version": "0.6.5", + "version": "0.6.6", "source": { "type": "git", "url": "https://github.com/utopia-php/vcs.git", - "reference": "104e47ea8e38c156ec0e0bd415caa3dcd5046fe2" + "reference": "e538264cfee5e3efdfe1771efba04750cf20b2c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/vcs/zipball/104e47ea8e38c156ec0e0bd415caa3dcd5046fe2", - "reference": "104e47ea8e38c156ec0e0bd415caa3dcd5046fe2", + "url": "https://api.github.com/repos/utopia-php/vcs/zipball/e538264cfee5e3efdfe1771efba04750cf20b2c4", + "reference": "e538264cfee5e3efdfe1771efba04750cf20b2c4", "shasum": "" }, "require": { @@ -2542,9 +2541,9 @@ ], "support": { "issues": "https://github.com/utopia-php/vcs/issues", - "source": "https://github.com/utopia-php/vcs/tree/0.6.5" + "source": "https://github.com/utopia-php/vcs/tree/0.6.6" }, - "time": "2024-01-08T17:11:12+00:00" + "time": "2024-05-17T09:36:30+00:00" }, { "name": "utopia-php/websocket", @@ -2731,16 +2730,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.38.2", + "version": "0.38.6", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "51284668529e2b10ed933412a42b603c76cded23" + "reference": "d7016d6d72545e84709892faca972eb4bf5bd699" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/51284668529e2b10ed933412a42b603c76cded23", - "reference": "51284668529e2b10ed933412a42b603c76cded23", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/d7016d6d72545e84709892faca972eb4bf5bd699", + "reference": "d7016d6d72545e84709892faca972eb4bf5bd699", "shasum": "" }, "require": { @@ -2776,9 +2775,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.38.2" + "source": "https://github.com/appwrite/sdk-generator/tree/0.38.6" }, - "time": "2024-04-25T07:49:29+00:00" + "time": "2024-05-20T18:00:16+00:00" }, { "name": "doctrine/deprecations", @@ -2899,16 +2898,16 @@ }, { "name": "laravel/pint", - "version": "v1.15.3", + "version": "v1.16.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "3600b5d17aff52f6100ea4921849deacbbeb8656" + "reference": "1b3a3dc5bc6a81ff52828ba7277621f1d49d6d98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/3600b5d17aff52f6100ea4921849deacbbeb8656", - "reference": "3600b5d17aff52f6100ea4921849deacbbeb8656", + "url": "https://api.github.com/repos/laravel/pint/zipball/1b3a3dc5bc6a81ff52828ba7277621f1d49d6d98", + "reference": "1b3a3dc5bc6a81ff52828ba7277621f1d49d6d98", "shasum": "" }, "require": { @@ -2919,11 +2918,11 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.54.0", - "illuminate/view": "^10.48.8", - "larastan/larastan": "^2.9.5", - "laravel-zero/framework": "^10.3.0", - "mockery/mockery": "^1.6.11", + "friendsofphp/php-cs-fixer": "^3.57.1", + "illuminate/view": "^10.48.10", + "larastan/larastan": "^2.9.6", + "laravel-zero/framework": "^10.4.0", + "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^1.15.1", "pestphp/pest": "^2.34.7" }, @@ -2961,7 +2960,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-04-30T15:02:26+00:00" + "time": "2024-05-21T18:08:25+00:00" }, { "name": "matthiasmullie/minify", @@ -3377,16 +3376,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.4.0", + "version": "5.4.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "298d2febfe79d03fe714eb871d5538da55205b1a" + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/298d2febfe79d03fe714eb871d5538da55205b1a", - "reference": "298d2febfe79d03fe714eb871d5538da55205b1a", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", "shasum": "" }, "require": { @@ -3435,9 +3434,9 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.0" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" }, - "time": "2024-04-09T21:13:58+00:00" + "time": "2024-05-21T05:55:05+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -3568,16 +3567,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.28.0", + "version": "1.29.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "cd06d6b1a1b3c75b0b83f97577869fd85a3cd4fb" + "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/cd06d6b1a1b3c75b0b83f97577869fd85a3cd4fb", - "reference": "cd06d6b1a1b3c75b0b83f97577869fd85a3cd4fb", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/536889f2b340489d328f5ffb7b02bb6b183ddedc", + "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc", "shasum": "" }, "require": { @@ -3609,9 +3608,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.28.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.0" }, - "time": "2024-04-03T18:51:33+00:00" + "time": "2024-05-06T12:04:23+00:00" }, { "name": "phpunit/php-code-coverage", @@ -5476,5 +5475,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/docs/references/functions/create-build.md b/docs/references/functions/create-build.md deleted file mode 100644 index 31a288a35a..0000000000 --- a/docs/references/functions/create-build.md +++ /dev/null @@ -1 +0,0 @@ -Create a new build for an Appwrite Function deployment. This endpoint can be used to retry a failed build. \ No newline at end of file diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 08fb899992..6b34f9db65 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -163,6 +163,7 @@ class Exception extends \Exception public const BUILD_NOT_FOUND = 'build_not_found'; public const BUILD_NOT_READY = 'build_not_ready'; public const BUILD_IN_PROGRESS = 'build_in_progress'; + public const BUILD_ALREADY_COMPLETED = 'build_already_completed'; /** Execution */ public const EXECUTION_NOT_FOUND = 'execution_not_found'; diff --git a/src/Appwrite/Platform/Workers/Builds.php b/src/Appwrite/Platform/Workers/Builds.php index 7f43d776ce..02ca13ae85 100644 --- a/src/Appwrite/Platform/Workers/Builds.php +++ b/src/Appwrite/Platform/Workers/Builds.php @@ -8,6 +8,7 @@ use Appwrite\Event\Usage; use Appwrite\Messaging\Adapter\Realtime; use Appwrite\Utopia\Response\Model\Deployment; use Appwrite\Vcs\Comment; +use Exception; use Executor\Executor; use Swoole\Coroutine as Co; use Utopia\Cache\Cache; @@ -156,9 +157,9 @@ class Builds extends Action $startTime = DateTime::now(); $durationStart = \microtime(true); $buildId = $deployment->getAttribute('buildId', ''); + $build = $dbForProject->getDocument('builds', $buildId); $isNewBuild = empty($buildId); - - if ($isNewBuild) { + if ($build->isEmpty()) { $buildId = ID::unique(); $build = $dbForProject->createDocument('builds', new Document([ '$id' => $buildId, @@ -180,6 +181,9 @@ class Builds extends Action $deployment->setAttribute('buildId', $build->getId()); $deployment->setAttribute('buildInternalId', $build->getInternalId()); $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment); + } elseif ($build->getAttribute('status') === 'canceled') { + Console::info('Build has been canceled'); + return; } else { $build = $dbForProject->getDocument('builds', $buildId); } @@ -221,6 +225,12 @@ class Builds extends Action $stdout = ''; $stderr = ''; Console::execute('mkdir -p /tmp/builds/' . \escapeshellcmd($buildId), '', $stdout, $stderr); + + if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') { + Console::info('Build has been canceled'); + return; + } + $exit = Console::execute($gitCloneCommand, '', $stdout, $stderr); if ($exit !== 0) { @@ -397,6 +407,11 @@ class Builds extends Action $response = null; $err = null; + if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') { + Console::info('Build has been canceled'); + return; + } + Co::join([ Co\go(function () use ($executor, &$response, $project, $deployment, $source, $function, $runtime, $vars, $command, &$err) { try { @@ -463,6 +478,10 @@ class Builds extends Action ]); if ($err) { + if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') { + Console::info('Build has been canceled'); + return; + } throw $err; } @@ -492,6 +511,11 @@ class Builds extends Action $function = $dbForProject->updateDocument('functions', $function->getId(), $function); } + if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') { + Console::info('Build has been canceled'); + return; + } + /** Update function schedule */ // Inform scheduler if function is still active @@ -502,6 +526,11 @@ class Builds extends Action ->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment'))); Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule)); } catch (\Throwable $th) { + if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') { + Console::info('Build has been canceled'); + return; + } + $endTime = DateTime::now(); $durationEnd = \microtime(true); $build->setAttribute('endTime', $endTime); diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 6a8dc322e3..4b2a8e0bc7 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -434,6 +434,73 @@ class FunctionsCustomServerTest extends Scope return array_merge($data, ['deploymentId' => $deploymentId]); } + /** + * @depends testUpdate + */ + public function testCancelDeploymentBuild($data): void + { + // Create a new deployment to cancel + $folder = 'php'; + $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz"; + $this->packageCode($folder); + + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/deployments', array_merge([ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'code' => new CURLFile($code, 'application/x-gzip', \basename($code)), + 'activate' => true + ]); + + $deploymentId = $deployment['body']['$id'] ?? ''; + + $this->assertEquals(202, $deployment['headers']['status-code']); + $this->assertNotEmpty($deployment['body']['$id']); + $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); + $this->assertEquals('index.php', $deployment['body']['entrypoint']); + + // Poll until deployment is in progress + while (true) { + $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/' . $deploymentId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + if ( + $deployment['headers']['status-code'] >= 400 + || $deployment['body']['status'] === 'building' + ) { + break; + } + + \sleep(1); + } + + $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertEquals('building', $deployment['body']['status']); + + // Cancel the deployment build + $cancel = $this->client->call(Client::METHOD_PATCH, '/functions/' . $data['functionId'] . '/deployments/' . $deploymentId . '/build', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals(200, $cancel['headers']['status-code']); + $this->assertEquals('canceled', $cancel['body']['status']); + + // Confirm the deployment is canceled + $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/' . $deploymentId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertEquals('canceled', $deployment['body']['status']); + } + /** * @depends testUpdate */ @@ -523,9 +590,9 @@ class FunctionsCustomServerTest extends Scope ], $this->getHeaders())); $this->assertEquals($function['headers']['status-code'], 200); - $this->assertEquals($function['body']['total'], 2); + $this->assertEquals($function['body']['total'], 3); $this->assertIsArray($function['body']['deployments']); - $this->assertCount(2, $function['body']['deployments']); + $this->assertCount(3, $function['body']['deployments']); /** * Test search queries @@ -538,9 +605,9 @@ class FunctionsCustomServerTest extends Scope ])); $this->assertEquals($function['headers']['status-code'], 200); - $this->assertEquals(2, $function['body']['total']); + $this->assertEquals(3, $function['body']['total']); $this->assertIsArray($function['body']['deployments']); - $this->assertCount(2, $function['body']['deployments']); + $this->assertCount(3, $function['body']['deployments']); $this->assertEquals($function['body']['deployments'][0]['$id'], $data['deploymentId']); $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([ @@ -565,7 +632,7 @@ class FunctionsCustomServerTest extends Scope ]); $this->assertEquals($function['headers']['status-code'], 200); - $this->assertCount(1, $function['body']['deployments']); + $this->assertCount(2, $function['body']['deployments']); $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([ 'content-type' => 'application/json', @@ -577,7 +644,7 @@ class FunctionsCustomServerTest extends Scope ]); $this->assertEquals($function['headers']['status-code'], 200); - $this->assertCount(2, $function['body']['deployments']); + $this->assertCount(3, $function['body']['deployments']); $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([ 'content-type' => 'application/json', @@ -599,9 +666,9 @@ class FunctionsCustomServerTest extends Scope ])); $this->assertEquals($function['headers']['status-code'], 200); - $this->assertEquals(2, $function['body']['total']); + $this->assertEquals(3, $function['body']['total']); $this->assertIsArray($function['body']['deployments']); - $this->assertCount(2, $function['body']['deployments']); + $this->assertCount(3, $function['body']['deployments']); $this->assertEquals($function['body']['deployments'][0]['$id'], $data['deploymentId']); $function = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments', array_merge([ @@ -612,9 +679,9 @@ class FunctionsCustomServerTest extends Scope ])); $this->assertEquals($function['headers']['status-code'], 200); - $this->assertEquals(2, $function['body']['total']); + $this->assertEquals(3, $function['body']['total']); $this->assertIsArray($function['body']['deployments']); - $this->assertCount(2, $function['body']['deployments']); + $this->assertCount(3, $function['body']['deployments']); $this->assertEquals($function['body']['deployments'][0]['$id'], $data['deploymentId']); return $data;