Use orchestrator PHP SDK for builds

This commit is contained in:
Chirag Aggarwal
2026-05-23 08:45:42 +05:30
parent 88c1693da5
commit 2e6106ecf5
4 changed files with 159 additions and 91 deletions
+8 -2
View File
@@ -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"
}]
}
Generated
+76 -2
View File
@@ -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": {
+34 -42
View File
@@ -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,
);
}
}
@@ -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(