From 2e6106ecf50c0d3dfc60c45e438bb6a84e358eb7 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Sat, 23 May 2026 08:45:42 +0530 Subject: [PATCH] Use orchestrator PHP SDK for builds --- composer.json | 10 ++- composer.lock | 78 ++++++++++++++++- src/Appwrite/Builds/OrchestratorClient.php | 76 ++++++++-------- .../Modules/Functions/Workers/Builds.php | 86 +++++++++---------- 4 files changed, 159 insertions(+), 91 deletions(-) diff --git a/composer.json b/composer.json index 34a0238b7a..85dec3edb5 100644 --- a/composer.json +++ b/composer.json @@ -96,7 +96,8 @@ "spomky-labs/otphp": "11.*", "webonyx/graphql-php": "15.32.*", "league/csv": "9.14.*", - "enshrined/svg-sanitize": "0.22.*" + "enshrined/svg-sanitize": "0.22.*", + "open-runtimes/orchestrator-client-php": "dev-add-jobs-sdk" }, "require-dev": { "ext-fileinfo": "*", @@ -119,5 +120,10 @@ "php-http/discovery": true, "tbachert/spi": true } - } + }, + "repositories": [{ + "name": "open-runtimes-orchestrator-client-php", + "type": "vcs", + "url": "https://github.com/open-runtimes/orchestrator-client-php" + }] } diff --git a/composer.lock b/composer.lock index a0a687d8ae..2c59c6da51 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "597066d71be48add0c649828d820a505", + "content-hash": "585b14725484cb2786ecad186f6284da", "packages": [ { "name": "adhocore/jwt", @@ -1224,6 +1224,78 @@ ], "time": "2023-11-08T09:30:43+00:00" }, + { + "name": "open-runtimes/orchestrator-client-php", + "version": "dev-add-jobs-sdk", + "source": { + "type": "git", + "url": "https://github.com/open-runtimes/orchestrator-client-php.git", + "reference": "5b44ad8f056d180e63368f844d01d167f706b13e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/open-runtimes/orchestrator-client-php/zipball/5b44ad8f056d180e63368f844d01d167f706b13e", + "reference": "5b44ad8f056d180e63368f844d01d167f706b13e", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "php": ">=8.3", + "utopia-php/fetch": "^1.1" + }, + "require-dev": { + "laravel/pint": "^1.22", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^13.1", + "rector/rector": "^2.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "OpenRuntimes\\Orchestrator\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "OpenRuntimes\\Orchestrator\\Tests\\": "tests/" + } + }, + "scripts": { + "format": [ + "vendor/bin/pint" + ], + "format:check": [ + "vendor/bin/pint --test" + ], + "analyze": [ + "vendor/bin/phpstan analyze --memory-limit=512M" + ], + "refactor": [ + "vendor/bin/rector process" + ], + "refactor:check": [ + "vendor/bin/rector process --dry-run" + ], + "test": [ + "vendor/bin/phpunit" + ], + "fix": [ + "@refactor", + "@analyze", + "@format" + ] + }, + "license": [ + "MIT" + ], + "description": "PHP SDK for the Open Runtimes orchestrator Jobs API.", + "support": { + "source": "https://github.com/open-runtimes/orchestrator-client-php/tree/add-jobs-sdk", + "issues": "https://github.com/open-runtimes/orchestrator-client-php/issues" + }, + "time": "2026-05-22T18:46:15+00:00" + }, { "name": "open-telemetry/api", "version": "1.9.0", @@ -8550,7 +8622,9 @@ ], "aliases": [], "minimum-stability": "dev", - "stability-flags": {}, + "stability-flags": { + "open-runtimes/orchestrator-client-php": 20 + }, "prefer-stable": true, "prefer-lowest": false, "platform": { diff --git a/src/Appwrite/Builds/OrchestratorClient.php b/src/Appwrite/Builds/OrchestratorClient.php index ec825f4c36..0621b76cd1 100644 --- a/src/Appwrite/Builds/OrchestratorClient.php +++ b/src/Appwrite/Builds/OrchestratorClient.php @@ -4,6 +4,11 @@ namespace Appwrite\Builds; use Executor\Exception as ExecutorException; use Executor\Exception\Timeout as ExecutorTimeout; +use OpenRuntimes\Orchestrator\Client as OrchestratorSdkClient; +use OpenRuntimes\Orchestrator\DTO\JobRequest; +use OpenRuntimes\Orchestrator\Exception\ApiException as OrchestratorApiException; +use OpenRuntimes\Orchestrator\Exception\OrchestratorException; +use OpenRuntimes\Orchestrator\Exception\TimeoutException as OrchestratorTimeout; use Utopia\System\System; class OrchestratorClient @@ -17,59 +22,46 @@ class OrchestratorClient $this->apiKey = System::getEnv('_APP_ORCHESTRATOR_API_KEY', ''); } - public function createJob(array $payload, int $timeout): array + public function createJob(JobRequest $request, int $timeout): array { - return $this->call('POST', '/v1/jobs', $payload, $timeout); + try { + $response = $this->client()->jobs()->create($request, $timeout); + + return [ + 'id' => $response->id, + 'status' => $response->status->value, + ]; + } catch (OrchestratorTimeout $e) { + throw new ExecutorTimeout($e->getMessage(), $e->timeoutSeconds, previous: $e); + } catch (OrchestratorApiException $e) { + throw new ExecutorException($e->body !== '' ? $e->body : $e->getMessage(), $e->statusCode, $e); + } catch (OrchestratorException $e) { + throw new ExecutorException($e->getMessage(), $e->getCode(), $e); + } } public function deleteJob(string $jobId): void { - $this->call('DELETE', '/v1/jobs/' . \rawurlencode($jobId), [], 30); + try { + $this->client()->jobs()->delete($jobId); + } catch (OrchestratorTimeout $e) { + throw new ExecutorTimeout($e->getMessage(), $e->timeoutSeconds, previous: $e); + } catch (OrchestratorApiException $e) { + throw new ExecutorException($e->body !== '' ? $e->body : $e->getMessage(), $e->statusCode, $e); + } catch (OrchestratorException $e) { + throw new ExecutorException($e->getMessage(), $e->getCode(), $e); + } } - private function call(string $method, string $path, array $payload, int $timeout): array + private function client(): OrchestratorSdkClient { if (empty($this->endpoint)) { throw new ExecutorException('Orchestrator host is not configured'); } - $headers = ['content-type: application/json']; - if (!empty($this->apiKey)) { - $headers[] = 'authorization: Bearer ' . $this->apiKey; - } - - $ch = \curl_init($this->endpoint . $path); - \curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); - \curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - \curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - \curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - \curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); - - if ($method !== 'GET' && $method !== 'DELETE') { - \curl_setopt($ch, CURLOPT_POSTFIELDS, \json_encode($payload)); - } - - $body = \curl_exec($ch); - $status = \curl_getinfo($ch, CURLINFO_HTTP_CODE); - $error = \curl_errno($ch); - $errorMessage = \curl_error($ch); - - if ($error) { - if ($error === CURLE_OPERATION_TIMEDOUT) { - throw new ExecutorTimeout('Orchestrator request timed out', $timeout); - } - throw new ExecutorException($errorMessage, $status); - } - - if ($status >= 400) { - throw new ExecutorException(\is_string($body) ? $body : 'Orchestrator request failed', $status); - } - - if (empty($body)) { - return []; - } - - $decoded = \json_decode((string) $body, true); - return \is_array($decoded) ? $decoded : []; + return new OrchestratorSdkClient( + endpoint: $this->endpoint, + apiKey: $this->apiKey !== '' ? $this->apiKey : null, + ); } } diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index e6c0c98787..863bf1a4f9 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -21,6 +21,12 @@ use Appwrite\Vcs\Comment; use Exception; use Executor\Exception\Timeout as ExecutorTimeout; use Executor\Executor; +use OpenRuntimes\Orchestrator\DTO\Artifact\ArchiveArtifact; +use OpenRuntimes\Orchestrator\DTO\Artifact\DownloadArtifact; +use OpenRuntimes\Orchestrator\DTO\Artifact\UploadArtifact; +use OpenRuntimes\Orchestrator\DTO\Callback; +use OpenRuntimes\Orchestrator\DTO\JobRequest; +use OpenRuntimes\Orchestrator\Enum\CallbackEvent; use Swoole\Coroutine as Co; use Utopia\Cache\Cache; use Utopia\Config\Config; @@ -1386,65 +1392,55 @@ class Builds extends Action $environment['OPEN_RUNTIMES_OUTPUT_DIRECTORY'] = $outputDirectory; } + $encodedSourceToken = \rawurlencode($sourceToken); + $sourceUrl = "{$base}/{$resourcePath}/{$resourceId}/deployments/{$deploymentId}/artifacts/source?{$projectQuery}&token={$encodedSourceToken}"; $artifacts = [ - [ - 'id' => 'source', - 'type' => 'download', - 'in' => "{$base}/{$resourcePath}/{$resourceId}/deployments/{$deploymentId}/artifacts/source?{$projectQuery}&token=" . \rawurlencode($sourceToken), - 'out' => 'code.tar.gz', - 'headers' => $appwriteProjectHeader, - ], + new DownloadArtifact('source', $sourceUrl, 'code.tar.gz', headers: $appwriteProjectHeader), ]; if ($version === 'v2') { - $artifacts[] = [ - 'id' => 'build', - 'type' => 'archive', - 'in' => 'build-output', - 'out' => 'build.tar.gz', - 'format' => 'tar.gz', - 'depends' => 'job', - ]; + $artifacts[] = new ArchiveArtifact('build', 'build-output', 'build.tar.gz', depends: 'job'); } - $artifacts[] = [ - 'id' => 'upload', - 'type' => 'upload', - 'in' => 'build.tar.gz', - 'out' => "{$base}/{$resourcePath}/{$resourceId}/deployments/{$deploymentId}/artifacts/build?{$projectQuery}&token=" . \rawurlencode($buildToken), - 'depends' => $version === 'v2' ? 'build' : 'job', - 'headers' => $appwriteProjectHeader, - 'chunked' => true, - ]; + $encodedBuildToken = \rawurlencode($buildToken); + $buildUrl = "{$base}/{$resourcePath}/{$resourceId}/deployments/{$deploymentId}/artifacts/build?{$projectQuery}&token={$encodedBuildToken}"; + $artifacts[] = new UploadArtifact( + 'upload', + 'build.tar.gz', + $buildUrl, + depends: $version === 'v2' ? 'build' : 'job', + headers: $appwriteProjectHeader, + chunked: true, + ); $client = new OrchestratorClient(); - $client->createJob([ - 'id' => $jobId, - 'meta' => [ + $client->createJob(new JobRequest( + id: $jobId, + meta: [ 'projectId' => $project->getId(), 'resourceId' => $resourceId, 'resourceType' => $resource->getCollection(), 'deploymentId' => $deploymentId, ], - 'image' => $runtime['image'], - 'command' => $buildCommand, - 'cpu' => $jobCpus, - 'memory' => $memory, - 'timeoutSeconds' => $timeout, - 'workspace' => '/tmp', - 'environment' => $environment, - 'artifacts' => $artifacts, - 'callback' => [ - 'url' => "{$callbackBase}/{$resourcePath}/{$resourceId}/deployments/{$deploymentId}/events?{$projectQuery}", - 'key' => System::getEnv('_APP_ORCHESTRATOR_CALLBACK_SECRET', System::getEnv('_APP_OPENSSL_KEY_V1', '')), - 'headers' => $appwriteProjectHeader, - 'events' => [ - 'orchestrator.job.log', - 'orchestrator.job.artifact', - 'orchestrator.job.exit', + image: (string) $runtime['image'], + command: $buildCommand, + cpu: $jobCpus, + memory: $memory, + timeoutSeconds: $timeout, + workspace: '/tmp', + environment: $environment, + artifacts: $artifacts, + callback: new Callback( + url: "{$callbackBase}/{$resourcePath}/{$resourceId}/deployments/{$deploymentId}/events?{$projectQuery}", + events: [ + CallbackEvent::Log, + CallbackEvent::Artifact, + CallbackEvent::Exit, ], - ], - ], $timeout); + key: System::getEnv('_APP_ORCHESTRATOR_CALLBACK_SECRET', System::getEnv('_APP_OPENSSL_KEY_V1', '')), + headers: $appwriteProjectHeader, + ), + ), $timeout); } protected function applyOrchestratorEvent(