Merge remote-tracking branch 'origin/feat-db-pools' into feat-refactor-tasks-only
This commit is contained in:
commit
c92cbbead2
15 changed files with 327 additions and 1222 deletions
19
.env
19
.env
|
@ -68,14 +68,14 @@ _APP_STORAGE_PREVIEW_LIMIT=20000000
|
||||||
_APP_FUNCTIONS_SIZE_LIMIT=30000000
|
_APP_FUNCTIONS_SIZE_LIMIT=30000000
|
||||||
_APP_FUNCTIONS_TIMEOUT=900
|
_APP_FUNCTIONS_TIMEOUT=900
|
||||||
_APP_FUNCTIONS_BUILD_TIMEOUT=900
|
_APP_FUNCTIONS_BUILD_TIMEOUT=900
|
||||||
_APP_FUNCTIONS_CONTAINERS=10
|
_APP_FUNCTIONS_CPUS=1
|
||||||
_APP_FUNCTIONS_CPUS=0
|
_APP_FUNCTIONS_MEMORY=512
|
||||||
_APP_FUNCTIONS_MEMORY=0
|
_APP_FUNCTIONS_INACTIVE_THRESHOLD=600
|
||||||
_APP_FUNCTIONS_MEMORY_SWAP=0
|
_APP_FUNCTIONS_RUNTIMES_NETWORK=openruntimes-runtimes
|
||||||
_APP_FUNCTIONS_INACTIVE_THRESHOLD=60
|
_APP_FUNCTIONS_CONNECTION_STORAGE=file://localhost
|
||||||
OPEN_RUNTIMES_NETWORK=appwrite_runtimes
|
|
||||||
_APP_EXECUTOR_SECRET=your-secret-key
|
_APP_EXECUTOR_SECRET=your-secret-key
|
||||||
_APP_EXECUTOR_HOST=http://appwrite-executor/v1
|
_APP_EXECUTOR_HOST=http://exc1/v1
|
||||||
|
_APP_FUNCTIONS_RUNTIMES=
|
||||||
_APP_MAINTENANCE_INTERVAL=86400
|
_APP_MAINTENANCE_INTERVAL=86400
|
||||||
_APP_MAINTENANCE_RETENTION_CACHE=2592000
|
_APP_MAINTENANCE_RETENTION_CACHE=2592000
|
||||||
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600
|
_APP_MAINTENANCE_RETENTION_EXECUTION=1209600
|
||||||
|
@ -86,6 +86,5 @@ _APP_USAGE_DATABASE_INTERVAL=15
|
||||||
_APP_USAGE_STATS=enabled
|
_APP_USAGE_STATS=enabled
|
||||||
_APP_LOGGING_PROVIDER=
|
_APP_LOGGING_PROVIDER=
|
||||||
_APP_LOGGING_CONFIG=
|
_APP_LOGGING_CONFIG=
|
||||||
DOCKERHUB_PULL_USERNAME=
|
_APP_DOCKER_HUB_USERNAME=
|
||||||
DOCKERHUB_PULL_PASSWORD=
|
_APP_DOCKER_HUB_PASSWORD=
|
||||||
DOCKERHUB_PULL_EMAIL=
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# Version 1.2.0
|
||||||
|
|
||||||
|
- Replace Appwrite executor with OpenRuntimes Executor [#4650](https://github.com/appwrite/appwrite/pull/4650)
|
||||||
|
|
||||||
# Version 1.1.0
|
# Version 1.1.0
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
|
|
|
@ -246,13 +246,10 @@ ENV _APP_SERVER=swoole \
|
||||||
_APP_SMS_FROM= \
|
_APP_SMS_FROM= \
|
||||||
_APP_FUNCTIONS_SIZE_LIMIT=30000000 \
|
_APP_FUNCTIONS_SIZE_LIMIT=30000000 \
|
||||||
_APP_FUNCTIONS_TIMEOUT=900 \
|
_APP_FUNCTIONS_TIMEOUT=900 \
|
||||||
_APP_FUNCTIONS_CONTAINERS=10 \
|
|
||||||
_APP_FUNCTIONS_CPUS=1 \
|
_APP_FUNCTIONS_CPUS=1 \
|
||||||
_APP_FUNCTIONS_MEMORY=128 \
|
_APP_FUNCTIONS_MEMORY=128 \
|
||||||
_APP_FUNCTIONS_MEMORY_SWAP=128 \
|
|
||||||
_APP_EXECUTOR_SECRET=a-random-secret \
|
_APP_EXECUTOR_SECRET=a-random-secret \
|
||||||
_APP_EXECUTOR_HOST=http://appwrite-executor/v1 \
|
_APP_EXECUTOR_HOST=http://exc1/v1 \
|
||||||
_APP_EXECUTOR_RUNTIME_NETWORK=appwrite_runtimes \
|
|
||||||
_APP_SETUP=self-hosted \
|
_APP_SETUP=self-hosted \
|
||||||
_APP_VERSION=$VERSION \
|
_APP_VERSION=$VERSION \
|
||||||
_APP_USAGE_STATS=enabled \
|
_APP_USAGE_STATS=enabled \
|
||||||
|
@ -349,7 +346,6 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||||
chmod +x /usr/local/bin/install && \
|
chmod +x /usr/local/bin/install && \
|
||||||
chmod +x /usr/local/bin/migrate && \
|
chmod +x /usr/local/bin/migrate && \
|
||||||
chmod +x /usr/local/bin/realtime && \
|
chmod +x /usr/local/bin/realtime && \
|
||||||
chmod +x /usr/local/bin/executor && \
|
|
||||||
chmod +x /usr/local/bin/schedule && \
|
chmod +x /usr/local/bin/schedule && \
|
||||||
chmod +x /usr/local/bin/sdks && \
|
chmod +x /usr/local/bin/sdks && \
|
||||||
chmod +x /usr/local/bin/specs && \
|
chmod +x /usr/local/bin/specs && \
|
||||||
|
|
|
@ -701,7 +701,7 @@ return [
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => '_APP_FUNCTIONS_CONTAINERS',
|
'name' => '_APP_FUNCTIONS_CONTAINERS',
|
||||||
'description' => 'The maximum number of containers Appwrite is allowed to keep alive in the background for function environments. Running containers allow faster execution time as there is no need to recreate each container every time a function gets executed. The default value is 10.',
|
'description' => 'Deprecated since 1.2.0. Runtimes now timeout by inactivity using \'_APP_FUNCTIONS_INACTIVE_THRESHOLD\'.',
|
||||||
'introduction' => '0.7.0',
|
'introduction' => '0.7.0',
|
||||||
'default' => '10',
|
'default' => '10',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
@ -728,7 +728,7 @@ return [
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => '_APP_FUNCTIONS_MEMORY_SWAP',
|
'name' => '_APP_FUNCTIONS_MEMORY_SWAP',
|
||||||
'description' => 'The maximum amount of swap memory a single cloud function is allowed to use in megabytes. The default value is empty. When it\'s empty, swap memory limit will be disabled.',
|
'description' => 'Deprecated since 1.2.0. High use of swap memory is not recommended to preserve harddrive health.',
|
||||||
'introduction' => '0.7.0',
|
'introduction' => '0.7.0',
|
||||||
'default' => '0',
|
'default' => '0',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
@ -782,7 +782,7 @@ return [
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => '_APP_FUNCTIONS_INACTIVE_THRESHOLD',
|
'name' => '_APP_FUNCTIONS_INACTIVE_THRESHOLD',
|
||||||
'description' => 'The minimum time a function can be inactive before it\'s container is shutdown and put to sleep. The default value is 60 seconds',
|
'description' => 'The minimum time a function can be inactive before it\'s container is shutdown and put to sleep. The default value is 60 seconds.',
|
||||||
'introduction' => '0.13.0',
|
'introduction' => '0.13.0',
|
||||||
'default' => '60',
|
'default' => '60',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
@ -791,7 +791,7 @@ return [
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'DOCKERHUB_PULL_USERNAME',
|
'name' => 'DOCKERHUB_PULL_USERNAME',
|
||||||
'description' => 'The username for hub.docker.com. This variable is used to pull images from hub.docker.com.',
|
'description' => 'Deprecated with 1.2.0, use \'_APP_DOCKER_HUB_USERNAME\' instead!',
|
||||||
'introduction' => '0.10.0',
|
'introduction' => '0.10.0',
|
||||||
'default' => '',
|
'default' => '',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
@ -800,7 +800,7 @@ return [
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'DOCKERHUB_PULL_PASSWORD',
|
'name' => 'DOCKERHUB_PULL_PASSWORD',
|
||||||
'description' => 'The password for hub.docker.com. This variable is used to pull images from hub.docker.com.',
|
'description' => 'Deprecated with 1.2.0, use \'_APP_DOCKER_HUB_PASSWORD\' instead!',
|
||||||
'introduction' => '0.10.0',
|
'introduction' => '0.10.0',
|
||||||
'default' => '',
|
'default' => '',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
@ -809,7 +809,7 @@ return [
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'DOCKERHUB_PULL_EMAIL',
|
'name' => 'DOCKERHUB_PULL_EMAIL',
|
||||||
'description' => 'The email for hub.docker.com. This variable is used to pull images from hub.docker.com.',
|
'description' => 'Deprecated since 1.2.0. Email is no longer needed.',
|
||||||
'introduction' => '0.10.0',
|
'introduction' => '0.10.0',
|
||||||
'default' => '',
|
'default' => '',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
@ -818,13 +818,49 @@ return [
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'OPEN_RUNTIMES_NETWORK',
|
'name' => 'OPEN_RUNTIMES_NETWORK',
|
||||||
'description' => 'The docker network used for communication between the executor and runtimes. Change this if you have altered the default network names.',
|
'description' => 'Deprecated with 1.2.0, use \'_APP_FUNCTIONS_RUNTIMES_NETWORK\' instead!',
|
||||||
'introduction' => '0.13.0',
|
'introduction' => '0.13.0',
|
||||||
'default' => 'appwrite_runtimes',
|
'default' => 'appwrite_runtimes',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'question' => '',
|
'question' => '',
|
||||||
'filter' => ''
|
'filter' => ''
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'name' => '_APP_FUNCTIONS_RUNTIMES_NETWORK',
|
||||||
|
'description' => 'The docker network used for communication between the executor and runtimes. Change this if you have altered the default network names.',
|
||||||
|
'introduction' => '1.2.0',
|
||||||
|
'default' => 'openruntimes-runtimes',
|
||||||
|
'required' => false,
|
||||||
|
'question' => '',
|
||||||
|
'filter' => ''
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => '_APP_DOCKER_HUB_USERNAME',
|
||||||
|
'description' => 'The username for hub.docker.com. This variable is used to pull images from hub.docker.com.',
|
||||||
|
'introduction' => '1.2.0',
|
||||||
|
'default' => '',
|
||||||
|
'required' => false,
|
||||||
|
'question' => '',
|
||||||
|
'filter' => ''
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => '_APP_DOCKER_HUB_PASSWORD',
|
||||||
|
'description' => 'The password for hub.docker.com. This variable is used to pull images from hub.docker.com.',
|
||||||
|
'introduction' => '1.2.0',
|
||||||
|
'default' => '',
|
||||||
|
'required' => false,
|
||||||
|
'question' => '',
|
||||||
|
'filter' => ''
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => '_APP_FUNCTIONS_CONNECTION_STORAGE',
|
||||||
|
'description' => 'DSN record of storage driver where Open Runtimes Executor stores output of Appwrite Function Deployment builds.',
|
||||||
|
'introduction' => '1.2.0',
|
||||||
|
'default' => '',
|
||||||
|
'required' => false,
|
||||||
|
'question' => '',
|
||||||
|
'filter' => ''
|
||||||
|
]
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|
|
@ -1148,13 +1148,12 @@ App::post('/v1/functions/:functionId/executions')
|
||||||
$executionResponse = $executor->createExecution(
|
$executionResponse = $executor->createExecution(
|
||||||
projectId: $project->getId(),
|
projectId: $project->getId(),
|
||||||
deploymentId: $deployment->getId(),
|
deploymentId: $deployment->getId(),
|
||||||
path: $build->getAttribute('outputPath', ''),
|
payload: $data,
|
||||||
vars: $vars,
|
variables: $vars,
|
||||||
data: $data,
|
|
||||||
entrypoint: $deployment->getAttribute('entrypoint', ''),
|
|
||||||
runtime: $function->getAttribute('runtime', ''),
|
|
||||||
timeout: $function->getAttribute('timeout', 0),
|
timeout: $function->getAttribute('timeout', 0),
|
||||||
baseImage: $runtime['image']
|
image: $runtime['image'],
|
||||||
|
source: $build->getAttribute('outputPath', ''),
|
||||||
|
entrypoint: $deployment->getAttribute('entrypoint', ''),
|
||||||
);
|
);
|
||||||
|
|
||||||
/** Update execution status */
|
/** Update execution status */
|
||||||
|
|
802
app/executor.php
802
app/executor.php
|
@ -1,802 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
require_once __DIR__ . '/../vendor/autoload.php';
|
|
||||||
|
|
||||||
use Appwrite\Runtimes\Runtimes;
|
|
||||||
use Swoole\ConnectionPool;
|
|
||||||
use Swoole\Http\Request as SwooleRequest;
|
|
||||||
use Swoole\Http\Response as SwooleResponse;
|
|
||||||
use Swoole\Http\Server;
|
|
||||||
use Swoole\Process;
|
|
||||||
use Swoole\Runtime;
|
|
||||||
use Swoole\Timer;
|
|
||||||
use Utopia\App;
|
|
||||||
use Utopia\CLI\Console;
|
|
||||||
use Utopia\Database\DateTime;
|
|
||||||
use Utopia\Logger\Log;
|
|
||||||
use Utopia\Logger\Logger;
|
|
||||||
use Utopia\Orchestration\Adapter\DockerCLI;
|
|
||||||
use Utopia\Orchestration\Orchestration;
|
|
||||||
use Utopia\Storage\Device;
|
|
||||||
use Utopia\Storage\Device\Local;
|
|
||||||
use Utopia\Storage\Device\Backblaze;
|
|
||||||
use Utopia\Storage\Device\DOSpaces;
|
|
||||||
use Utopia\Storage\Device\Linode;
|
|
||||||
use Utopia\Storage\Device\Wasabi;
|
|
||||||
use Utopia\Storage\Device\S3;
|
|
||||||
use Utopia\Storage\Storage;
|
|
||||||
use Utopia\Swoole\Request;
|
|
||||||
use Utopia\Swoole\Response;
|
|
||||||
use Utopia\Validator\ArrayList;
|
|
||||||
use Utopia\Validator\Assoc;
|
|
||||||
use Utopia\Validator\Boolean;
|
|
||||||
use Utopia\Validator\Range;
|
|
||||||
use Utopia\Validator\Text;
|
|
||||||
|
|
||||||
|
|
||||||
Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL);
|
|
||||||
|
|
||||||
/** Constants */
|
|
||||||
const MAINTENANCE_INTERVAL = 3600; // 3600 seconds = 1 hour
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a Swoole table to store runtime information
|
|
||||||
*/
|
|
||||||
$activeRuntimes = new Swoole\Table(1024);
|
|
||||||
$activeRuntimes->column('id', Swoole\Table::TYPE_STRING, 256);
|
|
||||||
$activeRuntimes->column('created', Swoole\Table::TYPE_INT, 8);
|
|
||||||
$activeRuntimes->column('updated', Swoole\Table::TYPE_INT, 8);
|
|
||||||
$activeRuntimes->column('name', Swoole\Table::TYPE_STRING, 128);
|
|
||||||
$activeRuntimes->column('status', Swoole\Table::TYPE_STRING, 128);
|
|
||||||
$activeRuntimes->column('key', Swoole\Table::TYPE_STRING, 256);
|
|
||||||
$activeRuntimes->create();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create orchestration pool
|
|
||||||
*/
|
|
||||||
$orchestrationPool = new ConnectionPool(function () {
|
|
||||||
$dockerUser = App::getEnv('DOCKERHUB_PULL_USERNAME', null);
|
|
||||||
$dockerPass = App::getEnv('DOCKERHUB_PULL_PASSWORD', null);
|
|
||||||
$orchestration = new Orchestration(new DockerCLI($dockerUser, $dockerPass));
|
|
||||||
return $orchestration;
|
|
||||||
}, 10);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create logger instance
|
|
||||||
*/
|
|
||||||
$providerName = App::getEnv('_APP_LOGGING_PROVIDER', '');
|
|
||||||
$providerConfig = App::getEnv('_APP_LOGGING_CONFIG', '');
|
|
||||||
$logger = null;
|
|
||||||
|
|
||||||
if (!empty($providerName) && !empty($providerConfig) && Logger::hasProvider($providerName)) {
|
|
||||||
$classname = '\\Utopia\\Logger\\Adapter\\' . \ucfirst($providerName);
|
|
||||||
$adapter = new $classname($providerConfig);
|
|
||||||
$logger = new Logger($adapter);
|
|
||||||
}
|
|
||||||
|
|
||||||
function logError(Throwable $error, string $action, Utopia\Route $route = null)
|
|
||||||
{
|
|
||||||
global $logger;
|
|
||||||
|
|
||||||
if ($logger) {
|
|
||||||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
|
||||||
|
|
||||||
$log = new Log();
|
|
||||||
$log->setNamespace("executor");
|
|
||||||
$log->setServer(\gethostname());
|
|
||||||
$log->setVersion($version);
|
|
||||||
$log->setType(Log::TYPE_ERROR);
|
|
||||||
$log->setMessage($error->getMessage());
|
|
||||||
|
|
||||||
if ($route) {
|
|
||||||
$log->addTag('method', $route->getMethod());
|
|
||||||
$log->addTag('url', $route->getPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
$log->addTag('code', $error->getCode());
|
|
||||||
$log->addTag('verboseType', get_class($error));
|
|
||||||
|
|
||||||
$log->addExtra('file', $error->getFile());
|
|
||||||
$log->addExtra('line', $error->getLine());
|
|
||||||
$log->addExtra('trace', $error->getTraceAsString());
|
|
||||||
$log->addExtra('detailedTrace', $error->getTrace());
|
|
||||||
|
|
||||||
$log->setAction($action);
|
|
||||||
|
|
||||||
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
|
|
||||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
|
||||||
|
|
||||||
$responseCode = $logger->addLog($log);
|
|
||||||
Console::info('Executor log pushed with status code: ' . $responseCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console::error('[Error] Type: ' . get_class($error));
|
|
||||||
Console::error('[Error] Message: ' . $error->getMessage());
|
|
||||||
Console::error('[Error] File: ' . $error->getFile());
|
|
||||||
Console::error('[Error] Line: ' . $error->getLine());
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStorageDevice($root): Device
|
|
||||||
{
|
|
||||||
switch (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL)) {
|
|
||||||
case Storage::DEVICE_LOCAL:
|
|
||||||
default:
|
|
||||||
return new Local($root);
|
|
||||||
case Storage::DEVICE_S3:
|
|
||||||
$s3AccessKey = App::getEnv('_APP_STORAGE_S3_ACCESS_KEY', '');
|
|
||||||
$s3SecretKey = App::getEnv('_APP_STORAGE_S3_SECRET', '');
|
|
||||||
$s3Region = App::getEnv('_APP_STORAGE_S3_REGION', '');
|
|
||||||
$s3Bucket = App::getEnv('_APP_STORAGE_S3_BUCKET', '');
|
|
||||||
$s3Acl = 'private';
|
|
||||||
return new S3($root, $s3AccessKey, $s3SecretKey, $s3Bucket, $s3Region, $s3Acl);
|
|
||||||
case Storage::DEVICE_DO_SPACES:
|
|
||||||
$doSpacesAccessKey = App::getEnv('_APP_STORAGE_DO_SPACES_ACCESS_KEY', '');
|
|
||||||
$doSpacesSecretKey = App::getEnv('_APP_STORAGE_DO_SPACES_SECRET', '');
|
|
||||||
$doSpacesRegion = App::getEnv('_APP_STORAGE_DO_SPACES_REGION', '');
|
|
||||||
$doSpacesBucket = App::getEnv('_APP_STORAGE_DO_SPACES_BUCKET', '');
|
|
||||||
$doSpacesAcl = 'private';
|
|
||||||
return new DOSpaces($root, $doSpacesAccessKey, $doSpacesSecretKey, $doSpacesBucket, $doSpacesRegion, $doSpacesAcl);
|
|
||||||
case Storage::DEVICE_BACKBLAZE:
|
|
||||||
$backblazeAccessKey = App::getEnv('_APP_STORAGE_BACKBLAZE_ACCESS_KEY', '');
|
|
||||||
$backblazeSecretKey = App::getEnv('_APP_STORAGE_BACKBLAZE_SECRET', '');
|
|
||||||
$backblazeRegion = App::getEnv('_APP_STORAGE_BACKBLAZE_REGION', '');
|
|
||||||
$backblazeBucket = App::getEnv('_APP_STORAGE_BACKBLAZE_BUCKET', '');
|
|
||||||
$backblazeAcl = 'private';
|
|
||||||
return new Backblaze($root, $backblazeAccessKey, $backblazeSecretKey, $backblazeBucket, $backblazeRegion, $backblazeAcl);
|
|
||||||
case Storage::DEVICE_LINODE:
|
|
||||||
$linodeAccessKey = App::getEnv('_APP_STORAGE_LINODE_ACCESS_KEY', '');
|
|
||||||
$linodeSecretKey = App::getEnv('_APP_STORAGE_LINODE_SECRET', '');
|
|
||||||
$linodeRegion = App::getEnv('_APP_STORAGE_LINODE_REGION', '');
|
|
||||||
$linodeBucket = App::getEnv('_APP_STORAGE_LINODE_BUCKET', '');
|
|
||||||
$linodeAcl = 'private';
|
|
||||||
return new Linode($root, $linodeAccessKey, $linodeSecretKey, $linodeBucket, $linodeRegion, $linodeAcl);
|
|
||||||
case Storage::DEVICE_WASABI:
|
|
||||||
$wasabiAccessKey = App::getEnv('_APP_STORAGE_WASABI_ACCESS_KEY', '');
|
|
||||||
$wasabiSecretKey = App::getEnv('_APP_STORAGE_WASABI_SECRET', '');
|
|
||||||
$wasabiRegion = App::getEnv('_APP_STORAGE_WASABI_REGION', '');
|
|
||||||
$wasabiBucket = App::getEnv('_APP_STORAGE_WASABI_BUCKET', '');
|
|
||||||
$wasabiAcl = 'private';
|
|
||||||
return new Wasabi($root, $wasabiAccessKey, $wasabiSecretKey, $wasabiBucket, $wasabiRegion, $wasabiAcl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
App::post('/v1/runtimes')
|
|
||||||
->desc("Create a new runtime server")
|
|
||||||
->param('runtimeId', '', new Text(64), 'Unique runtime ID.')
|
|
||||||
->param('source', '', new Text(0), 'Path to source files.')
|
|
||||||
->param('destination', '', new Text(0), 'Destination folder to store build files into.', true)
|
|
||||||
->param('vars', [], new Assoc(), 'Environment Variables required for the build.')
|
|
||||||
->param('commands', [], new ArrayList(new Text(1024), 100), 'Commands required to build the container. Maximum of 100 commands are allowed, each 1024 characters long.')
|
|
||||||
->param('runtime', '', new Text(128), 'Runtime for the cloud function.')
|
|
||||||
->param('baseImage', '', new Text(128), 'Base image name of the runtime.')
|
|
||||||
->param('entrypoint', '', new Text(256), 'Entrypoint of the code file.', true)
|
|
||||||
->param('remove', false, new Boolean(), 'Remove a runtime after execution.')
|
|
||||||
->param('workdir', '', new Text(256), 'Working directory.', true)
|
|
||||||
->inject('orchestrationPool')
|
|
||||||
->inject('activeRuntimes')
|
|
||||||
->inject('response')
|
|
||||||
->action(function (string $runtimeId, string $source, string $destination, array $vars, array $commands, string $runtime, string $baseImage, string $entrypoint, bool $remove, string $workdir, $orchestrationPool, $activeRuntimes, Response $response) {
|
|
||||||
if ($activeRuntimes->exists($runtimeId)) {
|
|
||||||
if ($activeRuntimes->get($runtimeId)['status'] == 'pending') {
|
|
||||||
throw new \Exception('A runtime with the same ID is already being created. Attempt a execution soon.', 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception('Runtime already exists.', 409);
|
|
||||||
}
|
|
||||||
|
|
||||||
$container = [];
|
|
||||||
$containerId = '';
|
|
||||||
$stdout = '';
|
|
||||||
$stderr = '';
|
|
||||||
$startTime = DateTime::now();
|
|
||||||
$startTimeUnix = (new \DateTime($startTime))->getTimestamp();
|
|
||||||
$endTimeUnix = 0;
|
|
||||||
$orchestration = $orchestrationPool->get();
|
|
||||||
|
|
||||||
$secret = \bin2hex(\random_bytes(16));
|
|
||||||
|
|
||||||
if (!$remove) {
|
|
||||||
$activeRuntimes->set($runtimeId, [
|
|
||||||
'id' => $containerId,
|
|
||||||
'name' => $runtimeId,
|
|
||||||
'created' => $startTimeUnix,
|
|
||||||
'updated' => $endTimeUnix,
|
|
||||||
'status' => 'pending',
|
|
||||||
'key' => $secret,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Console::info('Building container : ' . $runtimeId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Temporary file paths in the executor
|
|
||||||
*/
|
|
||||||
$tmpSource = "/tmp/$runtimeId/src/code.tar.gz";
|
|
||||||
$tmpBuild = "/tmp/$runtimeId/builds/code.tar.gz";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy code files from source to a temporary location on the executor
|
|
||||||
*/
|
|
||||||
$sourceDevice = getStorageDevice("/");
|
|
||||||
$localDevice = new Local();
|
|
||||||
$buffer = $sourceDevice->read($source);
|
|
||||||
if (!$localDevice->write($tmpSource, $buffer)) {
|
|
||||||
throw new Exception('Failed to copy source code to temporary directory', 500);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create the mount folder
|
|
||||||
*/
|
|
||||||
if (!\file_exists(\dirname($tmpBuild))) {
|
|
||||||
if (!@\mkdir(\dirname($tmpBuild), 0755, true)) {
|
|
||||||
throw new Exception("Failed to create temporary directory", 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create container
|
|
||||||
*/
|
|
||||||
$vars = \array_merge($vars, [
|
|
||||||
'INTERNAL_RUNTIME_KEY' => $secret,
|
|
||||||
'INTERNAL_RUNTIME_ENTRYPOINT' => $entrypoint,
|
|
||||||
]);
|
|
||||||
$vars = array_map(fn ($v) => strval($v), $vars);
|
|
||||||
$orchestration
|
|
||||||
->setCpus((int) App::getEnv('_APP_FUNCTIONS_CPUS', 0))
|
|
||||||
->setMemory((int) App::getEnv('_APP_FUNCTIONS_MEMORY', 0))
|
|
||||||
->setSwap((int) App::getEnv('_APP_FUNCTIONS_MEMORY_SWAP', 0));
|
|
||||||
|
|
||||||
/** Keep the container alive if we have commands to be executed */
|
|
||||||
$entrypoint = !empty($commands) ? [
|
|
||||||
'tail',
|
|
||||||
'-f',
|
|
||||||
'/dev/null'
|
|
||||||
] : [];
|
|
||||||
|
|
||||||
$containerId = $orchestration->run(
|
|
||||||
image: $baseImage,
|
|
||||||
name: $runtimeId,
|
|
||||||
hostname: $runtimeId,
|
|
||||||
vars: $vars,
|
|
||||||
command: $entrypoint,
|
|
||||||
labels: [
|
|
||||||
'openruntimes-id' => $runtimeId,
|
|
||||||
'openruntimes-type' => 'runtime',
|
|
||||||
'openruntimes-created' => strval($startTimeUnix),
|
|
||||||
'openruntimes-runtime' => $runtime,
|
|
||||||
],
|
|
||||||
workdir: $workdir,
|
|
||||||
volumes: [
|
|
||||||
\dirname($tmpSource) . ':/tmp:rw',
|
|
||||||
\dirname($tmpBuild) . ':/usr/code:rw'
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (empty($containerId)) {
|
|
||||||
throw new Exception('Failed to create build container', 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
$orchestration->networkConnect($runtimeId, App::getEnv('OPEN_RUNTIMES_NETWORK', 'appwrite_runtimes'));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute any commands if they were provided
|
|
||||||
*/
|
|
||||||
if (!empty($commands)) {
|
|
||||||
$status = $orchestration->execute(
|
|
||||||
name: $runtimeId,
|
|
||||||
command: $commands,
|
|
||||||
stdout: $stdout,
|
|
||||||
stderr: $stderr,
|
|
||||||
timeout: App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!$status) {
|
|
||||||
throw new Exception('Failed to build dependenices ' . $stderr, 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Move built code to expected build directory
|
|
||||||
*/
|
|
||||||
if (!empty($destination)) {
|
|
||||||
// Check if the build was successful by checking if file exists
|
|
||||||
if (!\file_exists($tmpBuild)) {
|
|
||||||
throw new Exception('Something went wrong during the build process', 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
$destinationDevice = getStorageDevice($destination);
|
|
||||||
$outputPath = $destinationDevice->getPath(\uniqid() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
|
|
||||||
|
|
||||||
$buffer = $localDevice->read($tmpBuild);
|
|
||||||
if (!$destinationDevice->write($outputPath, $buffer, $localDevice->getFileMimeType($tmpBuild))) {
|
|
||||||
throw new Exception('Failed to move built code to storage', 500);
|
|
||||||
};
|
|
||||||
|
|
||||||
$container['outputPath'] = $outputPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($stdout)) {
|
|
||||||
$stdout = 'Build Successful!';
|
|
||||||
}
|
|
||||||
|
|
||||||
$endTime = DateTime::now();
|
|
||||||
$endTimeUnix = (new \DateTime($endTime))->getTimestamp();
|
|
||||||
$duration = $endTimeUnix - $startTimeUnix;
|
|
||||||
|
|
||||||
$container = array_merge($container, [
|
|
||||||
'status' => 'ready',
|
|
||||||
'response' => \mb_strcut($stdout, 0, 1000000), // Limit to 1MB
|
|
||||||
'stderr' => \mb_strcut($stderr, 0, 1000000), // Limit to 1MB
|
|
||||||
'startTime' => $startTime,
|
|
||||||
'endTime' => $endTime,
|
|
||||||
'duration' => $duration,
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
|
||||||
if (!$remove) {
|
|
||||||
$activeRuntimes->set($runtimeId, [
|
|
||||||
'id' => $containerId,
|
|
||||||
'name' => $runtimeId,
|
|
||||||
'created' => $startTimeUnix,
|
|
||||||
'updated' => $endTimeUnix,
|
|
||||||
'status' => 'Up ' . \round($duration, 2) . 's',
|
|
||||||
'key' => $secret,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console::success('Build Stage completed in ' . ($duration) . ' seconds');
|
|
||||||
} catch (Throwable $th) {
|
|
||||||
Console::error('Build failed: ' . $th->getMessage() . $stdout);
|
|
||||||
|
|
||||||
throw new Exception($th->getMessage() . $stdout, 500);
|
|
||||||
} finally {
|
|
||||||
// Container cleanup
|
|
||||||
if ($remove) {
|
|
||||||
if (!empty($containerId)) {
|
|
||||||
// If container properly created
|
|
||||||
$orchestration->remove($containerId, true);
|
|
||||||
$activeRuntimes->del($runtimeId);
|
|
||||||
} else {
|
|
||||||
// If whole creation failed, but container might have been initialized
|
|
||||||
try {
|
|
||||||
// Try to remove with contaier name instead of ID
|
|
||||||
$orchestration->remove($runtimeId, true);
|
|
||||||
$activeRuntimes->del($runtimeId);
|
|
||||||
} catch (Throwable $th) {
|
|
||||||
// If fails, means initialization also failed.
|
|
||||||
// Contianer is not there, no need to remove
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release orchestration back to pool, we are done with it
|
|
||||||
$orchestrationPool->put($orchestration);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response
|
|
||||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
|
||||||
->json($container);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
App::get('/v1/runtimes')
|
|
||||||
->desc("List currently active runtimes")
|
|
||||||
->inject('activeRuntimes')
|
|
||||||
->inject('response')
|
|
||||||
->action(function ($activeRuntimes, Response $response) {
|
|
||||||
$runtimes = [];
|
|
||||||
|
|
||||||
foreach ($activeRuntimes as $runtime) {
|
|
||||||
$runtimes[] = $runtime;
|
|
||||||
}
|
|
||||||
|
|
||||||
$response
|
|
||||||
->setStatusCode(Response::STATUS_CODE_OK)
|
|
||||||
->json($runtimes);
|
|
||||||
});
|
|
||||||
|
|
||||||
App::get('/v1/runtimes/:runtimeId')
|
|
||||||
->desc("Get a runtime by its ID")
|
|
||||||
->param('runtimeId', '', new Text(64), 'Runtime unique ID.')
|
|
||||||
->inject('activeRuntimes')
|
|
||||||
->inject('response')
|
|
||||||
->action(function ($runtimeId, $activeRuntimes, Response $response) {
|
|
||||||
|
|
||||||
if (!$activeRuntimes->exists($runtimeId)) {
|
|
||||||
throw new Exception('Runtime not found', 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
$runtime = $activeRuntimes->get($runtimeId);
|
|
||||||
|
|
||||||
$response
|
|
||||||
->setStatusCode(Response::STATUS_CODE_OK)
|
|
||||||
->json($runtime);
|
|
||||||
});
|
|
||||||
|
|
||||||
App::delete('/v1/runtimes/:runtimeId')
|
|
||||||
->desc('Delete a runtime')
|
|
||||||
->param('runtimeId', '', new Text(64), 'Runtime unique ID.', false)
|
|
||||||
->inject('orchestrationPool')
|
|
||||||
->inject('activeRuntimes')
|
|
||||||
->inject('response')
|
|
||||||
->action(function (string $runtimeId, $orchestrationPool, $activeRuntimes, Response $response) {
|
|
||||||
|
|
||||||
if (!$activeRuntimes->exists($runtimeId)) {
|
|
||||||
throw new Exception('Runtime not found', 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console::info('Deleting runtime: ' . $runtimeId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$orchestration = $orchestrationPool->get();
|
|
||||||
$orchestration->remove($runtimeId, true);
|
|
||||||
$activeRuntimes->del($runtimeId);
|
|
||||||
Console::success('Removed runtime container: ' . $runtimeId);
|
|
||||||
} finally {
|
|
||||||
$orchestrationPool->put($orchestration);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove all the build containers with that same ID
|
|
||||||
// TODO:: Delete build containers
|
|
||||||
// foreach ($buildIds as $buildId) {
|
|
||||||
// try {
|
|
||||||
// Console::info('Deleting build container : ' . $buildId);
|
|
||||||
// $status = $orchestration->remove('build-' . $buildId, true);
|
|
||||||
// } catch (Throwable $th) {
|
|
||||||
// Console::error($th->getMessage());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
$response
|
|
||||||
->setStatusCode(Response::STATUS_CODE_OK)
|
|
||||||
->send();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
App::post('/v1/execution')
|
|
||||||
->desc('Create an execution')
|
|
||||||
->param('runtimeId', '', new Text(64), 'The runtimeID to execute.')
|
|
||||||
->param('vars', [], new Assoc(), 'Environment variables required for the build.')
|
|
||||||
->param('data', '', new Text(8192), 'Data to be forwarded to the function, this is user specified.', true)
|
|
||||||
->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Function maximum execution time in seconds.')
|
|
||||||
->inject('activeRuntimes')
|
|
||||||
->inject('response')
|
|
||||||
->action(
|
|
||||||
function (string $runtimeId, array $vars, string $data, $timeout, $activeRuntimes, Response $response) {
|
|
||||||
if (!$activeRuntimes->exists($runtimeId)) {
|
|
||||||
throw new Exception('Runtime not found. Please create the runtime.', 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
for ($i = 0; $i < 5; $i++) {
|
|
||||||
if ($activeRuntimes->get($runtimeId)['status'] === 'pending') {
|
|
||||||
Console::info('Waiting for runtime to be ready...');
|
|
||||||
sleep(1);
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($i === 4) {
|
|
||||||
throw new Exception('Runtime failed to launch in allocated time.', 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$runtime = $activeRuntimes->get($runtimeId);
|
|
||||||
$secret = $runtime['key'];
|
|
||||||
if (empty($secret)) {
|
|
||||||
throw new Exception('Runtime secret not found. Please re-create the runtime.', 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
Console::info('Executing Runtime: ' . $runtimeId);
|
|
||||||
|
|
||||||
$execution = [];
|
|
||||||
$executionStart = \microtime(true);
|
|
||||||
$stdout = '';
|
|
||||||
$stderr = '';
|
|
||||||
$res = '';
|
|
||||||
$statusCode = 0;
|
|
||||||
$errNo = -1;
|
|
||||||
$executorResponse = '';
|
|
||||||
|
|
||||||
$timeout ??= (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900);
|
|
||||||
|
|
||||||
$ch = \curl_init();
|
|
||||||
$body = \json_encode([
|
|
||||||
'variables' => $vars,
|
|
||||||
'payload' => $data,
|
|
||||||
'timeout' => $timeout
|
|
||||||
]);
|
|
||||||
\curl_setopt($ch, CURLOPT_URL, "http://" . $runtimeId . ":3000/");
|
|
||||||
\curl_setopt($ch, CURLOPT_POST, true);
|
|
||||||
\curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
|
||||||
\curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
\curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
|
|
||||||
\curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
|
|
||||||
|
|
||||||
\curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
||||||
'Content-Type: application/json',
|
|
||||||
'Content-Length: ' . \strlen($body),
|
|
||||||
'x-internal-challenge: ' . $secret,
|
|
||||||
'host: null'
|
|
||||||
]);
|
|
||||||
|
|
||||||
$executorResponse = \curl_exec($ch);
|
|
||||||
$executorResponse = json_decode($executorResponse, true);
|
|
||||||
|
|
||||||
$statusCode = \curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
||||||
|
|
||||||
$error = \curl_error($ch);
|
|
||||||
|
|
||||||
$errNo = \curl_errno($ch);
|
|
||||||
|
|
||||||
\curl_close($ch);
|
|
||||||
|
|
||||||
switch (true) {
|
|
||||||
/** No Error. */
|
|
||||||
case $errNo === 0:
|
|
||||||
break;
|
|
||||||
/** Runtime not ready for requests yet. 111 is the swoole error code for Connection Refused - see https://openswoole.com/docs/swoole-error-code */
|
|
||||||
case $errNo === 111:
|
|
||||||
throw new Exception('An internal curl error has occurred within the executor! Error Msg: ' . $error, 406);
|
|
||||||
/** Any other CURL error */
|
|
||||||
default:
|
|
||||||
throw new Exception('An internal curl error has occurred within the executor! Error Msg: ' . $error, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (true) {
|
|
||||||
case $statusCode >= 500:
|
|
||||||
$stderr = ($executorResponse ?? [])['stderr'] ?? 'Internal Runtime error.';
|
|
||||||
$stdout = ($executorResponse ?? [])['stdout'] ?? 'Internal Runtime error.';
|
|
||||||
break;
|
|
||||||
case $statusCode >= 100:
|
|
||||||
$stdout = $executorResponse['stdout'];
|
|
||||||
$res = $executorResponse['response'];
|
|
||||||
if (is_array($res)) {
|
|
||||||
$res = json_encode($res, JSON_UNESCAPED_UNICODE);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$stderr = ($executorResponse ?? [])['stderr'] ?? 'Execution failed.';
|
|
||||||
$stdout = ($executorResponse ?? [])['stdout'] ?? '';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$executionEnd = \microtime(true);
|
|
||||||
$executionTime = ($executionEnd - $executionStart);
|
|
||||||
$functionStatus = ($statusCode >= 500) ? 'failed' : 'completed';
|
|
||||||
|
|
||||||
Console::success('Function executed in ' . $executionTime . ' seconds, status: ' . $functionStatus);
|
|
||||||
|
|
||||||
$execution = [
|
|
||||||
'status' => $functionStatus,
|
|
||||||
'statusCode' => $statusCode,
|
|
||||||
'response' => \mb_strcut($res, 0, 1000000), // Limit to 1MB
|
|
||||||
'stdout' => \mb_strcut($stdout, 0, 1000000), // Limit to 1MB
|
|
||||||
'stderr' => \mb_strcut($stderr, 0, 1000000), // Limit to 1MB
|
|
||||||
'duration' => $executionTime,
|
|
||||||
];
|
|
||||||
|
|
||||||
/** Update swoole table */
|
|
||||||
$runtime['updated'] = \time();
|
|
||||||
$activeRuntimes->set($runtimeId, $runtime);
|
|
||||||
|
|
||||||
$response
|
|
||||||
->setStatusCode(Response::STATUS_CODE_OK)
|
|
||||||
->json($execution);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
App::setMode(App::MODE_TYPE_PRODUCTION); // Define Mode
|
|
||||||
|
|
||||||
$http = new Server("0.0.0.0", 80);
|
|
||||||
|
|
||||||
/** Set Resources */
|
|
||||||
App::setResource('orchestrationPool', fn() => $orchestrationPool);
|
|
||||||
App::setResource('activeRuntimes', fn() => $activeRuntimes);
|
|
||||||
|
|
||||||
/** Set callbacks */
|
|
||||||
App::error()
|
|
||||||
->inject('utopia')
|
|
||||||
->inject('error')
|
|
||||||
->inject('request')
|
|
||||||
->inject('response')
|
|
||||||
->action(function (App $utopia, throwable $error, Request $request, Response $response) {
|
|
||||||
$route = $utopia->match($request);
|
|
||||||
logError($error, "httpError", $route);
|
|
||||||
|
|
||||||
switch ($error->getCode()) {
|
|
||||||
case 400: // Error allowed publicly
|
|
||||||
case 401: // Error allowed publicly
|
|
||||||
case 402: // Error allowed publicly
|
|
||||||
case 403: // Error allowed publicly
|
|
||||||
case 404: // Error allowed publicly
|
|
||||||
case 406: // Error allowed publicly
|
|
||||||
case 409: // Error allowed publicly
|
|
||||||
case 412: // Error allowed publicly
|
|
||||||
case 425: // Error allowed publicly
|
|
||||||
case 429: // Error allowed publicly
|
|
||||||
case 501: // Error allowed publicly
|
|
||||||
case 503: // Error allowed publicly
|
|
||||||
$code = $error->getCode();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$code = 500; // All other errors get the generic 500 server error status code
|
|
||||||
}
|
|
||||||
|
|
||||||
$output = [
|
|
||||||
'message' => $error->getMessage(),
|
|
||||||
'code' => $error->getCode(),
|
|
||||||
'file' => $error->getFile(),
|
|
||||||
'line' => $error->getLine(),
|
|
||||||
'trace' => $error->getTrace(),
|
|
||||||
'version' => App::getEnv('_APP_VERSION', 'UNKNOWN')
|
|
||||||
];
|
|
||||||
|
|
||||||
$response
|
|
||||||
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
|
|
||||||
->addHeader('Expires', '0')
|
|
||||||
->addHeader('Pragma', 'no-cache')
|
|
||||||
->setStatusCode($code);
|
|
||||||
|
|
||||||
$response->json($output);
|
|
||||||
});
|
|
||||||
|
|
||||||
App::init()
|
|
||||||
->inject('request')
|
|
||||||
->action(function (Request $request) {
|
|
||||||
$secretKey = $request->getHeader('x-appwrite-executor-key', '');
|
|
||||||
if (empty($secretKey)) {
|
|
||||||
throw new Exception('Missing executor key', 401);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($secretKey !== App::getEnv('_APP_EXECUTOR_SECRET', '')) {
|
|
||||||
throw new Exception('Missing executor key', 401);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
$http->on('start', function ($http) {
|
|
||||||
global $orchestrationPool;
|
|
||||||
global $activeRuntimes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Warmup: make sure images are ready to run fast 🚀
|
|
||||||
*/
|
|
||||||
$runtimes = new Runtimes('v2');
|
|
||||||
$allowList = empty(App::getEnv('_APP_FUNCTIONS_RUNTIMES')) ? [] : \explode(',', App::getEnv('_APP_FUNCTIONS_RUNTIMES'));
|
|
||||||
$runtimes = $runtimes->getAll(true, $allowList);
|
|
||||||
foreach ($runtimes as $runtime) {
|
|
||||||
go(function () use ($runtime, $orchestrationPool) {
|
|
||||||
try {
|
|
||||||
$orchestration = $orchestrationPool->get();
|
|
||||||
Console::info('Warming up ' . $runtime['name'] . ' ' . $runtime['version'] . ' environment...');
|
|
||||||
$response = $orchestration->pull($runtime['image']);
|
|
||||||
if ($response) {
|
|
||||||
Console::success("Successfully Warmed up {$runtime['name']} {$runtime['version']}!");
|
|
||||||
} else {
|
|
||||||
Console::warning("Failed to Warmup {$runtime['name']} {$runtime['version']}!");
|
|
||||||
}
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
} finally {
|
|
||||||
$orchestrationPool->put($orchestration);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove residual runtimes
|
|
||||||
*/
|
|
||||||
Console::info('Removing orphan runtimes...');
|
|
||||||
try {
|
|
||||||
$orchestration = $orchestrationPool->get();
|
|
||||||
$orphans = $orchestration->list(['label' => 'openruntimes-type=runtime']);
|
|
||||||
} finally {
|
|
||||||
$orchestrationPool->put($orchestration);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($orphans as $runtime) {
|
|
||||||
go(function () use ($runtime, $orchestrationPool) {
|
|
||||||
try {
|
|
||||||
$orchestration = $orchestrationPool->get();
|
|
||||||
$orchestration->remove($runtime->getName(), true);
|
|
||||||
Console::success("Successfully removed {$runtime->getName()}");
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::error('Orphan runtime deletion failed: ' . $th->getMessage());
|
|
||||||
} finally {
|
|
||||||
$orchestrationPool->put($orchestration);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register handlers for shutdown
|
|
||||||
*/
|
|
||||||
@Process::signal(SIGINT, function () use ($http) {
|
|
||||||
$http->shutdown();
|
|
||||||
});
|
|
||||||
|
|
||||||
@Process::signal(SIGQUIT, function () use ($http) {
|
|
||||||
$http->shutdown();
|
|
||||||
});
|
|
||||||
|
|
||||||
@Process::signal(SIGKILL, function () use ($http) {
|
|
||||||
$http->shutdown();
|
|
||||||
});
|
|
||||||
|
|
||||||
@Process::signal(SIGTERM, function () use ($http) {
|
|
||||||
$http->shutdown();
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run a maintenance worker every MAINTENANCE_INTERVAL seconds to remove inactive runtimes
|
|
||||||
*/
|
|
||||||
Timer::tick(MAINTENANCE_INTERVAL * 1000, function () use ($orchestrationPool, $activeRuntimes) {
|
|
||||||
Console::warning("Running maintenance task ...");
|
|
||||||
foreach ($activeRuntimes as $runtime) {
|
|
||||||
$inactiveThreshold = \time() - App::getEnv('_APP_FUNCTIONS_INACTIVE_THRESHOLD', 60);
|
|
||||||
if ($runtime['updated'] < $inactiveThreshold) {
|
|
||||||
go(function () use ($runtime, $orchestrationPool, $activeRuntimes) {
|
|
||||||
try {
|
|
||||||
$orchestration = $orchestrationPool->get();
|
|
||||||
$orchestration->remove($runtime['name'], true);
|
|
||||||
$activeRuntimes->del($runtime['name']);
|
|
||||||
Console::success("Successfully removed {$runtime['name']}");
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::error('Inactive Runtime deletion failed: ' . $th->getMessage());
|
|
||||||
} finally {
|
|
||||||
$orchestrationPool->put($orchestration);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
$http->on('beforeShutdown', function () {
|
|
||||||
global $orchestrationPool;
|
|
||||||
Console::info('Cleaning up containers before shutdown...');
|
|
||||||
|
|
||||||
$orchestration = $orchestrationPool->get();
|
|
||||||
$functionsToRemove = $orchestration->list(['label' => 'openruntimes-type=runtime']);
|
|
||||||
$orchestrationPool->put($orchestration);
|
|
||||||
|
|
||||||
foreach ($functionsToRemove as $container) {
|
|
||||||
go(function () use ($orchestrationPool, $container) {
|
|
||||||
try {
|
|
||||||
$orchestration = $orchestrationPool->get();
|
|
||||||
$orchestration->remove($container->getId(), true);
|
|
||||||
Console::info('Removed container ' . $container->getName());
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
Console::error('Failed to remove container: ' . $container->getName());
|
|
||||||
} finally {
|
|
||||||
$orchestrationPool->put($orchestration);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) {
|
|
||||||
$request = new Request($swooleRequest);
|
|
||||||
$response = new Response($swooleResponse);
|
|
||||||
$app = new App('UTC');
|
|
||||||
|
|
||||||
try {
|
|
||||||
$app->run($request, $response);
|
|
||||||
} catch (\Throwable $th) {
|
|
||||||
logError($th, "serverError");
|
|
||||||
$swooleResponse->setStatusCode(500);
|
|
||||||
$output = [
|
|
||||||
'message' => 'Error: ' . $th->getMessage(),
|
|
||||||
'code' => 500,
|
|
||||||
'file' => $th->getFile(),
|
|
||||||
'line' => $th->getLine(),
|
|
||||||
'trace' => $th->getTrace()
|
|
||||||
];
|
|
||||||
$swooleResponse->end(\json_encode($output));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$http->start();
|
|
|
@ -66,7 +66,7 @@ services:
|
||||||
- appwrite-cache:/storage/cache:rw
|
- appwrite-cache:/storage/cache:rw
|
||||||
- appwrite-config:/storage/config:rw
|
- appwrite-config:/storage/config:rw
|
||||||
- appwrite-certificates:/storage/certificates:rw
|
- appwrite-certificates:/storage/certificates:rw
|
||||||
- appwrite-functions:/storage/functions:rw
|
- openruntimes-functions:/storage/functions:rw
|
||||||
depends_on:
|
depends_on:
|
||||||
- mariadb
|
- mariadb
|
||||||
- redis
|
- redis
|
||||||
|
@ -134,10 +134,8 @@ services:
|
||||||
- _APP_FUNCTIONS_SIZE_LIMIT
|
- _APP_FUNCTIONS_SIZE_LIMIT
|
||||||
- _APP_FUNCTIONS_TIMEOUT
|
- _APP_FUNCTIONS_TIMEOUT
|
||||||
- _APP_FUNCTIONS_BUILD_TIMEOUT
|
- _APP_FUNCTIONS_BUILD_TIMEOUT
|
||||||
- _APP_FUNCTIONS_CONTAINERS
|
|
||||||
- _APP_FUNCTIONS_CPUS
|
- _APP_FUNCTIONS_CPUS
|
||||||
- _APP_FUNCTIONS_MEMORY
|
- _APP_FUNCTIONS_MEMORY
|
||||||
- _APP_FUNCTIONS_MEMORY_SWAP
|
|
||||||
- _APP_FUNCTIONS_RUNTIMES
|
- _APP_FUNCTIONS_RUNTIMES
|
||||||
- _APP_EXECUTOR_SECRET
|
- _APP_EXECUTOR_SECRET
|
||||||
- _APP_EXECUTOR_HOST
|
- _APP_EXECUTOR_HOST
|
||||||
|
@ -259,8 +257,8 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- appwrite-uploads:/storage/uploads:rw
|
- appwrite-uploads:/storage/uploads:rw
|
||||||
- appwrite-cache:/storage/cache:rw
|
- appwrite-cache:/storage/cache:rw
|
||||||
- appwrite-functions:/storage/functions:rw
|
- openruntimes-functions:/storage/functions:rw
|
||||||
- appwrite-builds:/storage/builds:rw
|
- openruntimes-builds:/storage/builds:rw
|
||||||
- appwrite-certificates:/storage/certificates:rw
|
- appwrite-certificates:/storage/certificates:rw
|
||||||
environment:
|
environment:
|
||||||
- _APP_ENV
|
- _APP_ENV
|
||||||
|
@ -397,7 +395,7 @@ services:
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
- mariadb
|
- mariadb
|
||||||
- appwrite-executor
|
- openruntimes-executor
|
||||||
environment:
|
environment:
|
||||||
- _APP_ENV
|
- _APP_ENV
|
||||||
- _APP_OPENSSL_KEY_V1
|
- _APP_OPENSSL_KEY_V1
|
||||||
|
@ -414,66 +412,6 @@ services:
|
||||||
- _APP_EXECUTOR_SECRET
|
- _APP_EXECUTOR_SECRET
|
||||||
- _APP_EXECUTOR_HOST
|
- _APP_EXECUTOR_HOST
|
||||||
- _APP_USAGE_STATS
|
- _APP_USAGE_STATS
|
||||||
- DOCKERHUB_PULL_USERNAME
|
|
||||||
- DOCKERHUB_PULL_PASSWORD
|
|
||||||
|
|
||||||
appwrite-executor:
|
|
||||||
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
|
||||||
entrypoint: executor
|
|
||||||
<<: *x-logging
|
|
||||||
container_name: appwrite-executor
|
|
||||||
restart: unless-stopped
|
|
||||||
stop_signal: SIGINT
|
|
||||||
networks:
|
|
||||||
appwrite:
|
|
||||||
runtimes:
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
|
||||||
- appwrite-functions:/storage/functions:rw
|
|
||||||
- appwrite-builds:/storage/builds:rw
|
|
||||||
- /tmp:/tmp:rw
|
|
||||||
depends_on:
|
|
||||||
- redis
|
|
||||||
- mariadb
|
|
||||||
- appwrite
|
|
||||||
environment:
|
|
||||||
- _APP_ENV
|
|
||||||
- _APP_VERSION
|
|
||||||
- _APP_FUNCTIONS_TIMEOUT
|
|
||||||
- _APP_FUNCTIONS_BUILD_TIMEOUT
|
|
||||||
- _APP_FUNCTIONS_CONTAINERS
|
|
||||||
- _APP_FUNCTIONS_RUNTIMES
|
|
||||||
- _APP_FUNCTIONS_CPUS
|
|
||||||
- _APP_FUNCTIONS_MEMORY
|
|
||||||
- _APP_FUNCTIONS_MEMORY_SWAP
|
|
||||||
- _APP_FUNCTIONS_INACTIVE_THRESHOLD
|
|
||||||
- _APP_EXECUTOR_SECRET
|
|
||||||
- OPEN_RUNTIMES_NETWORK
|
|
||||||
- _APP_LOGGING_PROVIDER
|
|
||||||
- _APP_LOGGING_CONFIG
|
|
||||||
- _APP_STORAGE_DEVICE
|
|
||||||
- _APP_STORAGE_S3_ACCESS_KEY
|
|
||||||
- _APP_STORAGE_S3_SECRET
|
|
||||||
- _APP_STORAGE_S3_REGION
|
|
||||||
- _APP_STORAGE_S3_BUCKET
|
|
||||||
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
|
|
||||||
- _APP_STORAGE_DO_SPACES_SECRET
|
|
||||||
- _APP_STORAGE_DO_SPACES_REGION
|
|
||||||
- _APP_STORAGE_DO_SPACES_BUCKET
|
|
||||||
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY
|
|
||||||
- _APP_STORAGE_BACKBLAZE_SECRET
|
|
||||||
- _APP_STORAGE_BACKBLAZE_REGION
|
|
||||||
- _APP_STORAGE_BACKBLAZE_BUCKET
|
|
||||||
- _APP_STORAGE_LINODE_ACCESS_KEY
|
|
||||||
- _APP_STORAGE_LINODE_SECRET
|
|
||||||
- _APP_STORAGE_LINODE_REGION
|
|
||||||
- _APP_STORAGE_LINODE_BUCKET
|
|
||||||
- _APP_STORAGE_WASABI_ACCESS_KEY
|
|
||||||
- _APP_STORAGE_WASABI_SECRET
|
|
||||||
- _APP_STORAGE_WASABI_REGION
|
|
||||||
- _APP_STORAGE_WASABI_BUCKET
|
|
||||||
- DOCKERHUB_PULL_USERNAME
|
|
||||||
- DOCKERHUB_PULL_PASSWORD
|
|
||||||
|
|
||||||
appwrite-worker-mails:
|
appwrite-worker-mails:
|
||||||
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
|
||||||
|
@ -633,6 +571,32 @@ services:
|
||||||
- _APP_REDIS_USER
|
- _APP_REDIS_USER
|
||||||
- _APP_REDIS_PASS
|
- _APP_REDIS_PASS
|
||||||
|
|
||||||
|
openruntimes-executor:
|
||||||
|
container_name: openruntimes-executor
|
||||||
|
hostname: exc1
|
||||||
|
<<: *x-logging
|
||||||
|
stop_signal: SIGINT
|
||||||
|
image: openruntimes/executor:0.1.4
|
||||||
|
networks:
|
||||||
|
- appwrite
|
||||||
|
- openruntimes-runtimes
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- openruntimes-builds:/storage/builds:rw
|
||||||
|
- openruntimes-functions:/storage/functions:rw
|
||||||
|
- /tmp:/tmp:rw
|
||||||
|
environment:
|
||||||
|
- OPR_EXECUTOR_CONNECTION_STORAGE=$_APP_FUNCTIONS_CONNECTION_STORAGE
|
||||||
|
- OPR_EXECUTOR_INACTIVE_TRESHOLD=$_APP_FUNCTIONS_INACTIVE_THRESHOLD
|
||||||
|
- OPR_EXECUTOR_NETWORK=$_APP_FUNCTIONS_RUNTIMES_NETWORK
|
||||||
|
- OPR_EXECUTOR_DOCKER_HUB_USERNAME=$_APP_DOCKER_HUB_USERNAME
|
||||||
|
- OPR_EXECUTOR_DOCKER_HUB_PASSWORD=$_APP_DOCKER_HUB_PASSWORD
|
||||||
|
- OPR_EXECUTOR_ENV=$_APP_ENV
|
||||||
|
- OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES
|
||||||
|
- OPR_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
|
||||||
|
- OPR_EXECUTOR_LOGGING_PROVIDER=$_APP_LOGGING_PROVIDER
|
||||||
|
- OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
|
||||||
|
|
||||||
mariadb:
|
mariadb:
|
||||||
image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
|
image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
|
||||||
container_name: appwrite-mariadb
|
container_name: appwrite-mariadb
|
||||||
|
@ -696,8 +660,13 @@ services:
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
gateway:
|
gateway:
|
||||||
|
name: gateway
|
||||||
appwrite:
|
appwrite:
|
||||||
runtimes:
|
name: appwrite
|
||||||
|
openruntimes-runtimes:
|
||||||
|
name: openruntimes-runtimes
|
||||||
|
openruntimes-executors:
|
||||||
|
name: openruntimes-executors
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
appwrite-mariadb:
|
appwrite-mariadb:
|
||||||
|
@ -705,8 +674,7 @@ volumes:
|
||||||
appwrite-cache:
|
appwrite-cache:
|
||||||
appwrite-uploads:
|
appwrite-uploads:
|
||||||
appwrite-certificates:
|
appwrite-certificates:
|
||||||
appwrite-functions:
|
|
||||||
appwrite-builds:
|
|
||||||
appwrite-influxdb:
|
appwrite-influxdb:
|
||||||
appwrite-config:
|
appwrite-config:
|
||||||
appwrite-executor:
|
openruntimes-functions:
|
||||||
|
openruntimes-builds:
|
||||||
|
|
|
@ -150,20 +150,17 @@ class BuildsV1 extends Worker
|
||||||
return $carry;
|
return $carry;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
$baseImage = $runtime['image'];
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$response = $this->executor->createRuntime(
|
$response = $this->executor->createRuntime(
|
||||||
projectId: $project->getId(),
|
projectId: $project->getId(),
|
||||||
deploymentId: $deployment->getId(),
|
deploymentId: $deployment->getId(),
|
||||||
entrypoint: $deployment->getAttribute('entrypoint'),
|
|
||||||
source: $source,
|
source: $source,
|
||||||
destination: APP_STORAGE_BUILDS . "/app-{$project->getId()}",
|
image: $runtime['image'],
|
||||||
vars: $vars,
|
|
||||||
runtime: $key,
|
|
||||||
baseImage: $baseImage,
|
|
||||||
workdir: '/usr/code',
|
|
||||||
remove: true,
|
remove: true,
|
||||||
|
entrypoint: $deployment->getAttribute('entrypoint'),
|
||||||
|
workdir: '/usr/code',
|
||||||
|
destination: APP_STORAGE_BUILDS . "/app-{$project->getId()}",
|
||||||
|
variables: $vars,
|
||||||
commands: [
|
commands: [
|
||||||
'sh', '-c',
|
'sh', '-c',
|
||||||
'tar -zxf /tmp/code.tar.gz -C /usr/code && \
|
'tar -zxf /tmp/code.tar.gz -C /usr/code && \
|
||||||
|
@ -171,13 +168,16 @@ class BuildsV1 extends Worker
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$endTime = new \DateTime();
|
||||||
|
$endTime->setTimestamp($response['endTimeUnix']);
|
||||||
|
|
||||||
/** Update the build document */
|
/** Update the build document */
|
||||||
$build->setAttribute('endTime', $response['endTime']);
|
$build->setAttribute('endTime', DateTime::format($endTime));
|
||||||
$build->setAttribute('duration', $response['duration']);
|
$build->setAttribute('duration', \intval($response['duration']));
|
||||||
$build->setAttribute('status', $response['status']);
|
$build->setAttribute('status', $response['status']);
|
||||||
$build->setAttribute('outputPath', $response['outputPath']);
|
$build->setAttribute('outputPath', $response['outputPath']);
|
||||||
$build->setAttribute('stderr', $response['stderr']);
|
$build->setAttribute('stderr', $response['stderr']);
|
||||||
$build->setAttribute('stdout', $response['response']);
|
$build->setAttribute('stdout', $response['stdout']);
|
||||||
|
|
||||||
Console::success("Build id: $buildId created");
|
Console::success("Build id: $buildId created");
|
||||||
|
|
||||||
|
|
|
@ -470,16 +470,17 @@ class DeletesV1 extends Worker
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request executor to delete all deployment containers
|
* Request executor to delete all deployment containers
|
||||||
|
* TODO: Re-enable. Disabled for now because of proxy. Container killed after inactivity automatically.
|
||||||
*/
|
*/
|
||||||
Console::info("Requesting executor to delete all deployment containers for function " . $functionId);
|
// Console::info("Requesting executor to delete all deployment containers for function " . $functionId);
|
||||||
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
// $executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
||||||
foreach ($deploymentIds as $deploymentId) {
|
// foreach ($deploymentIds as $deploymentId) {
|
||||||
try {
|
// try {
|
||||||
$executor->deleteRuntime($projectId, $deploymentId);
|
// $executor->deleteRuntime($projectId, $deploymentId);
|
||||||
} catch (Throwable $th) {
|
// } catch (Throwable $th) {
|
||||||
Console::error($th->getMessage());
|
// Console::error($th->getMessage());
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -520,15 +521,16 @@ class DeletesV1 extends Worker
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request executor to delete the deployment container
|
* Request executor to delete the deployment container.
|
||||||
|
* TODO: Re-enable. Disabled for now because of proxy. Container killed after inactivity automatically.
|
||||||
*/
|
*/
|
||||||
Console::info("Requesting executor to delete deployment container for deployment " . $deploymentId);
|
// Console::info("Requesting executor to delete deployment container for deployment " . $deploymentId);
|
||||||
try {
|
// try {
|
||||||
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
// $executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
||||||
$executor->deleteRuntime($projectId, $deploymentId);
|
// $executor->deleteRuntime($projectId, $deploymentId);
|
||||||
} catch (Throwable $th) {
|
// } catch (Throwable $th) {
|
||||||
Console::error($th->getMessage());
|
// Console::error($th->getMessage());
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -289,13 +289,12 @@ class FunctionsV1 extends Worker
|
||||||
$executionResponse = $this->executor->createExecution(
|
$executionResponse = $this->executor->createExecution(
|
||||||
projectId: $project->getId(),
|
projectId: $project->getId(),
|
||||||
deploymentId: $deploymentId,
|
deploymentId: $deploymentId,
|
||||||
path: $build->getAttribute('outputPath', ''),
|
payload: $vars['APPWRITE_FUNCTION_DATA'] ?? '',
|
||||||
vars: $vars,
|
variables: $vars,
|
||||||
entrypoint: $deployment->getAttribute('entrypoint', ''),
|
|
||||||
data: $vars['APPWRITE_FUNCTION_DATA'] ?? '',
|
|
||||||
runtime: $function->getAttribute('runtime', ''),
|
|
||||||
timeout: $function->getAttribute('timeout', 0),
|
timeout: $function->getAttribute('timeout', 0),
|
||||||
baseImage: $runtime['image']
|
image: $runtime['image'],
|
||||||
|
source: $build->getAttribute('outputPath', ''),
|
||||||
|
entrypoint: $deployment->getAttribute('entrypoint', ''),
|
||||||
);
|
);
|
||||||
|
|
||||||
/** Update execution status */
|
/** Update execution status */
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
php -e /usr/src/code/app/executor.php -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
|
|
2
composer.lock
generated
2
composer.lock
generated
|
@ -5245,5 +5245,5 @@
|
||||||
"platform-overrides": {
|
"platform-overrides": {
|
||||||
"php": "8.0"
|
"php": "8.0"
|
||||||
},
|
},
|
||||||
"plugin-api-version": "2.3.0"
|
"plugin-api-version": "2.2.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ services:
|
||||||
- appwrite-cache:/storage/cache:rw
|
- appwrite-cache:/storage/cache:rw
|
||||||
- appwrite-config:/storage/config:rw
|
- appwrite-config:/storage/config:rw
|
||||||
- appwrite-certificates:/storage/certificates:rw
|
- appwrite-certificates:/storage/certificates:rw
|
||||||
- appwrite-functions:/storage/functions:rw
|
- openruntimes-functions:/storage/functions:rw
|
||||||
- ./phpunit.xml:/usr/src/code/phpunit.xml
|
- ./phpunit.xml:/usr/src/code/phpunit.xml
|
||||||
- ./tests:/usr/src/code/tests
|
- ./tests:/usr/src/code/tests
|
||||||
- ./app:/usr/src/code/app
|
- ./app:/usr/src/code/app
|
||||||
|
@ -160,10 +160,8 @@ services:
|
||||||
- _APP_FUNCTIONS_SIZE_LIMIT
|
- _APP_FUNCTIONS_SIZE_LIMIT
|
||||||
- _APP_FUNCTIONS_TIMEOUT
|
- _APP_FUNCTIONS_TIMEOUT
|
||||||
- _APP_FUNCTIONS_BUILD_TIMEOUT
|
- _APP_FUNCTIONS_BUILD_TIMEOUT
|
||||||
- _APP_FUNCTIONS_CONTAINERS
|
|
||||||
- _APP_FUNCTIONS_CPUS
|
- _APP_FUNCTIONS_CPUS
|
||||||
- _APP_FUNCTIONS_MEMORY
|
- _APP_FUNCTIONS_MEMORY
|
||||||
- _APP_FUNCTIONS_MEMORY_SWAP
|
|
||||||
- _APP_FUNCTIONS_RUNTIMES
|
- _APP_FUNCTIONS_RUNTIMES
|
||||||
- _APP_EXECUTOR_SECRET
|
- _APP_EXECUTOR_SECRET
|
||||||
- _APP_EXECUTOR_HOST
|
- _APP_EXECUTOR_HOST
|
||||||
|
@ -228,6 +226,7 @@ services:
|
||||||
- _APP_CONNECTIONS_DB_PROJECT
|
- _APP_CONNECTIONS_DB_PROJECT
|
||||||
- _APP_CONNECTIONS_CACHE
|
- _APP_CONNECTIONS_CACHE
|
||||||
- _APP_CONNECTIONS_PUBSUB
|
- _APP_CONNECTIONS_PUBSUB
|
||||||
|
- _APP_CONNECTIONS_QUEUE
|
||||||
- _APP_USAGE_STATS
|
- _APP_USAGE_STATS
|
||||||
- _APP_LOGGING_PROVIDER
|
- _APP_LOGGING_PROVIDER
|
||||||
- _APP_LOGGING_CONFIG
|
- _APP_LOGGING_CONFIG
|
||||||
|
@ -303,8 +302,8 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- appwrite-uploads:/storage/uploads:rw
|
- appwrite-uploads:/storage/uploads:rw
|
||||||
- appwrite-cache:/storage/cache:rw
|
- appwrite-cache:/storage/cache:rw
|
||||||
- appwrite-functions:/storage/functions:rw
|
- openruntimes-functions:/storage/functions:rw
|
||||||
- appwrite-builds:/storage/builds:rw
|
- openruntimes-builds:/storage/builds:rw
|
||||||
- appwrite-certificates:/storage/certificates:rw
|
- appwrite-certificates:/storage/certificates:rw
|
||||||
- ./app:/usr/src/code/app
|
- ./app:/usr/src/code/app
|
||||||
- ./src:/usr/src/code/src
|
- ./src:/usr/src/code/src
|
||||||
|
@ -466,7 +465,7 @@ services:
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
- mariadb
|
- mariadb
|
||||||
- appwrite-executor
|
- openruntimes-executor
|
||||||
environment:
|
environment:
|
||||||
- _APP_ENV
|
- _APP_ENV
|
||||||
- _APP_OPENSSL_KEY_V1
|
- _APP_OPENSSL_KEY_V1
|
||||||
|
@ -490,67 +489,6 @@ services:
|
||||||
- DOCKERHUB_PULL_USERNAME
|
- DOCKERHUB_PULL_USERNAME
|
||||||
- DOCKERHUB_PULL_PASSWORD
|
- DOCKERHUB_PULL_PASSWORD
|
||||||
|
|
||||||
appwrite-executor:
|
|
||||||
container_name: appwrite-executor
|
|
||||||
<<: *x-logging
|
|
||||||
entrypoint: executor
|
|
||||||
stop_signal: SIGINT
|
|
||||||
image: appwrite-dev
|
|
||||||
networks:
|
|
||||||
appwrite:
|
|
||||||
runtimes:
|
|
||||||
ports:
|
|
||||||
- 9519:80
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
|
||||||
- ./app:/usr/src/code/app
|
|
||||||
- ./src:/usr/src/code/src
|
|
||||||
- appwrite-functions:/storage/functions:rw
|
|
||||||
- appwrite-builds:/storage/builds:rw
|
|
||||||
- /tmp:/tmp:rw
|
|
||||||
depends_on:
|
|
||||||
- redis
|
|
||||||
- mariadb
|
|
||||||
- appwrite
|
|
||||||
environment:
|
|
||||||
- _APP_ENV
|
|
||||||
- _APP_VERSION
|
|
||||||
- _APP_FUNCTIONS_TIMEOUT
|
|
||||||
- _APP_FUNCTIONS_BUILD_TIMEOUT
|
|
||||||
- _APP_FUNCTIONS_CONTAINERS
|
|
||||||
- _APP_FUNCTIONS_RUNTIMES
|
|
||||||
- _APP_FUNCTIONS_CPUS
|
|
||||||
- _APP_FUNCTIONS_MEMORY
|
|
||||||
- _APP_FUNCTIONS_MEMORY_SWAP
|
|
||||||
- _APP_FUNCTIONS_INACTIVE_THRESHOLD
|
|
||||||
- _APP_EXECUTOR_SECRET
|
|
||||||
- OPEN_RUNTIMES_NETWORK
|
|
||||||
- _APP_LOGGING_PROVIDER
|
|
||||||
- _APP_LOGGING_CONFIG
|
|
||||||
- _APP_STORAGE_DEVICE
|
|
||||||
- _APP_STORAGE_S3_ACCESS_KEY
|
|
||||||
- _APP_STORAGE_S3_SECRET
|
|
||||||
- _APP_STORAGE_S3_REGION
|
|
||||||
- _APP_STORAGE_S3_BUCKET
|
|
||||||
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
|
|
||||||
- _APP_STORAGE_DO_SPACES_SECRET
|
|
||||||
- _APP_STORAGE_DO_SPACES_REGION
|
|
||||||
- _APP_STORAGE_DO_SPACES_BUCKET
|
|
||||||
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY
|
|
||||||
- _APP_STORAGE_BACKBLAZE_SECRET
|
|
||||||
- _APP_STORAGE_BACKBLAZE_REGION
|
|
||||||
- _APP_STORAGE_BACKBLAZE_BUCKET
|
|
||||||
- _APP_STORAGE_LINODE_ACCESS_KEY
|
|
||||||
- _APP_STORAGE_LINODE_SECRET
|
|
||||||
- _APP_STORAGE_LINODE_REGION
|
|
||||||
- _APP_STORAGE_LINODE_BUCKET
|
|
||||||
- _APP_STORAGE_WASABI_ACCESS_KEY
|
|
||||||
- _APP_STORAGE_WASABI_SECRET
|
|
||||||
- _APP_STORAGE_WASABI_REGION
|
|
||||||
- _APP_STORAGE_WASABI_BUCKET
|
|
||||||
- DOCKERHUB_PULL_USERNAME
|
|
||||||
- DOCKERHUB_PULL_PASSWORD
|
|
||||||
|
|
||||||
appwrite-worker-mails:
|
appwrite-worker-mails:
|
||||||
entrypoint: worker-mails
|
entrypoint: worker-mails
|
||||||
<<: *x-logging
|
<<: *x-logging
|
||||||
|
@ -751,6 +689,32 @@ services:
|
||||||
- _APP_REDIS_PASS
|
- _APP_REDIS_PASS
|
||||||
- _APP_CONNECTIONS_QUEUE
|
- _APP_CONNECTIONS_QUEUE
|
||||||
|
|
||||||
|
openruntimes-executor:
|
||||||
|
container_name: openruntimes-executor
|
||||||
|
hostname: exc1
|
||||||
|
<<: *x-logging
|
||||||
|
stop_signal: SIGINT
|
||||||
|
image: openruntimes/executor:0.1.4
|
||||||
|
networks:
|
||||||
|
- appwrite
|
||||||
|
- openruntimes-runtimes
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- openruntimes-builds:/storage/builds:rw
|
||||||
|
- openruntimes-functions:/storage/functions:rw
|
||||||
|
- /tmp:/tmp:rw
|
||||||
|
environment:
|
||||||
|
- OPR_EXECUTOR_CONNECTION_STORAGE=$_APP_FUNCTIONS_CONNECTION_STORAGE
|
||||||
|
- OPR_EXECUTOR_INACTIVE_TRESHOLD=$_APP_FUNCTIONS_INACTIVE_THRESHOLD
|
||||||
|
- OPR_EXECUTOR_NETWORK=$_APP_FUNCTIONS_RUNTIMES_NETWORK
|
||||||
|
- OPR_EXECUTOR_DOCKER_HUB_USERNAME=$_APP_DOCKER_HUB_USERNAME
|
||||||
|
- OPR_EXECUTOR_DOCKER_HUB_PASSWORD=$_APP_DOCKER_HUB_PASSWORD
|
||||||
|
- OPR_EXECUTOR_ENV=$_APP_ENV
|
||||||
|
- OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES
|
||||||
|
- OPR_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
|
||||||
|
- OPR_EXECUTOR_LOGGING_PROVIDER=$_APP_LOGGING_PROVIDER
|
||||||
|
- OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
|
||||||
|
|
||||||
mariadb:
|
mariadb:
|
||||||
image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
|
image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p
|
||||||
container_name: mariadb
|
container_name: mariadb
|
||||||
|
@ -913,8 +877,11 @@ services:
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
gateway:
|
gateway:
|
||||||
|
name: gateway
|
||||||
appwrite:
|
appwrite:
|
||||||
runtimes:
|
name: appwrite
|
||||||
|
openruntimes-runtimes:
|
||||||
|
name: openruntimes-runtimes
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
appwrite-mariadb:
|
appwrite-mariadb:
|
||||||
|
@ -922,9 +889,8 @@ volumes:
|
||||||
appwrite-cache:
|
appwrite-cache:
|
||||||
appwrite-uploads:
|
appwrite-uploads:
|
||||||
appwrite-certificates:
|
appwrite-certificates:
|
||||||
appwrite-functions:
|
|
||||||
appwrite-builds:
|
|
||||||
appwrite-influxdb:
|
appwrite-influxdb:
|
||||||
appwrite-config:
|
appwrite-config:
|
||||||
appwrite-executor:
|
openruntimes-functions:
|
||||||
|
openruntimes-builds:
|
||||||
# appwrite-chronograf:
|
# appwrite-chronograf:
|
||||||
|
|
|
@ -18,20 +18,29 @@ class Executor
|
||||||
public const METHOD_CONNECT = 'CONNECT';
|
public const METHOD_CONNECT = 'CONNECT';
|
||||||
public const METHOD_TRACE = 'TRACE';
|
public const METHOD_TRACE = 'TRACE';
|
||||||
|
|
||||||
private $endpoint;
|
private bool $selfSigned = false;
|
||||||
|
|
||||||
private $selfSigned = false;
|
private string $endpoint;
|
||||||
|
|
||||||
protected $headers = [
|
protected array $headers;
|
||||||
'content-type' => '',
|
|
||||||
];
|
protected int $cpus;
|
||||||
|
|
||||||
|
protected int $memory;
|
||||||
|
|
||||||
public function __construct(string $endpoint)
|
public function __construct(string $endpoint)
|
||||||
{
|
{
|
||||||
if (!filter_var($endpoint, FILTER_VALIDATE_URL)) {
|
if (!filter_var($endpoint, FILTER_VALIDATE_URL)) {
|
||||||
throw new Exception('Unsupported endpoint');
|
throw new Exception('Unsupported endpoint');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->endpoint = $endpoint;
|
$this->endpoint = $endpoint;
|
||||||
|
$this->cpus = \intval(App::getEnv('_APP_FUNCTIONS_CPUS', '1'));
|
||||||
|
$this->memory = intval(App::getEnv('_APP_FUNCTIONS_MEMORY', '512'));
|
||||||
|
$this->headers = [
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'authorization' => 'Bearer ' . App::getEnv('_APP_EXECUTOR_SECRET', ''),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,45 +51,41 @@ class Executor
|
||||||
* @param string $deploymentId
|
* @param string $deploymentId
|
||||||
* @param string $projectId
|
* @param string $projectId
|
||||||
* @param string $source
|
* @param string $source
|
||||||
* @param string $runtime
|
* @param string $image
|
||||||
* @param string $baseImage
|
|
||||||
* @param bool $remove
|
* @param bool $remove
|
||||||
* @param string $entrypoint
|
* @param string $entrypoint
|
||||||
* @param string $workdir
|
* @param string $workdir
|
||||||
* @param string $destinaction
|
* @param string $destination
|
||||||
* @param string $network
|
* @param array $variables
|
||||||
* @param array $vars
|
|
||||||
* @param array $commands
|
* @param array $commands
|
||||||
*/
|
*/
|
||||||
public function createRuntime(
|
public function createRuntime(
|
||||||
string $deploymentId,
|
string $deploymentId,
|
||||||
string $projectId,
|
string $projectId,
|
||||||
string $source,
|
string $source,
|
||||||
string $runtime,
|
string $image,
|
||||||
string $baseImage,
|
|
||||||
bool $remove = false,
|
bool $remove = false,
|
||||||
string $entrypoint = '',
|
string $entrypoint = '',
|
||||||
string $workdir = '',
|
string $workdir = '',
|
||||||
string $destination = '',
|
string $destination = '',
|
||||||
array $vars = [],
|
array $variables = [],
|
||||||
array $commands = []
|
array $commands = []
|
||||||
) {
|
) {
|
||||||
|
$runtimeId = "$projectId-$deploymentId";
|
||||||
$route = "/runtimes";
|
$route = "/runtimes";
|
||||||
$headers = [
|
$headers = [ 'x-opr-runtime-id' => $runtimeId ];
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-executor-key' => App::getEnv('_APP_EXECUTOR_SECRET', '')
|
|
||||||
];
|
|
||||||
$params = [
|
$params = [
|
||||||
'runtimeId' => "$projectId-$deploymentId",
|
'runtimeId' => $runtimeId,
|
||||||
'source' => $source,
|
'source' => $source,
|
||||||
'destination' => $destination,
|
'destination' => $destination,
|
||||||
'runtime' => $runtime,
|
'image' => $image,
|
||||||
'baseImage' => $baseImage,
|
|
||||||
'entrypoint' => $entrypoint,
|
'entrypoint' => $entrypoint,
|
||||||
'workdir' => $workdir,
|
'workdir' => $workdir,
|
||||||
'vars' => $vars,
|
'variables' => $variables,
|
||||||
'remove' => $remove,
|
'remove' => $remove,
|
||||||
'commands' => $commands
|
'commands' => $commands,
|
||||||
|
'cpus' => $this->cpus,
|
||||||
|
'memory' => $this->memory,
|
||||||
];
|
];
|
||||||
|
|
||||||
$timeout = (int) App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900);
|
$timeout = (int) App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900);
|
||||||
|
@ -96,25 +101,48 @@ class Executor
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete Runtime
|
* Create an execution
|
||||||
*
|
|
||||||
* Deletes a runtime and cleans up any containers remaining.
|
|
||||||
*
|
*
|
||||||
* @param string $projectId
|
* @param string $projectId
|
||||||
* @param string $deploymentId
|
* @param string $deploymentId
|
||||||
|
* @param string $payload
|
||||||
|
* @param array $variables
|
||||||
|
* @param int $timeout
|
||||||
|
* @param string $image
|
||||||
|
* @param string $source
|
||||||
|
* @param string $entrypoint
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function deleteRuntime(string $projectId, string $deploymentId)
|
public function createExecution(
|
||||||
{
|
string $projectId,
|
||||||
|
string $deploymentId,
|
||||||
|
string $payload,
|
||||||
|
array $variables,
|
||||||
|
int $timeout,
|
||||||
|
string $image,
|
||||||
|
string $source,
|
||||||
|
string $entrypoint,
|
||||||
|
) {
|
||||||
$runtimeId = "$projectId-$deploymentId";
|
$runtimeId = "$projectId-$deploymentId";
|
||||||
$route = "/runtimes/$runtimeId";
|
$route = '/runtimes/' . $runtimeId . '/execution';
|
||||||
$headers = [
|
$headers = [ 'x-opr-runtime-id' => $runtimeId ];
|
||||||
'content-type' => 'application/json',
|
$params = [
|
||||||
'x-appwrite-executor-key' => App::getEnv('_APP_EXECUTOR_SECRET', '')
|
'runtimeId' => $runtimeId,
|
||||||
|
'variables' => $variables,
|
||||||
|
'payload' => $payload,
|
||||||
|
'timeout' => $timeout,
|
||||||
|
|
||||||
|
'image' => $image,
|
||||||
|
'source' => $source,
|
||||||
|
'entrypoint' => $entrypoint,
|
||||||
|
'cpus' => $this->cpus,
|
||||||
|
'memory' => $this->memory,
|
||||||
];
|
];
|
||||||
|
|
||||||
$params = [];
|
$timeout = (int) App::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900);
|
||||||
|
|
||||||
$response = $this->call(self::METHOD_DELETE, $route, $headers, $params, true, 30);
|
$response = $this->call(self::METHOD_POST, $route, $headers, $params, true, $timeout);
|
||||||
|
|
||||||
$status = $response['headers']['status-code'];
|
$status = $response['headers']['status-code'];
|
||||||
if ($status >= 400) {
|
if ($status >= 400) {
|
||||||
|
@ -124,93 +152,6 @@ class Executor
|
||||||
return $response['body'];
|
return $response['body'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an execution
|
|
||||||
*
|
|
||||||
* @param string $projectId
|
|
||||||
* @param string $deploymentId
|
|
||||||
* @param string $path
|
|
||||||
* @param array $vars
|
|
||||||
* @param string $entrypoint
|
|
||||||
* @param string $data
|
|
||||||
* @param string runtime
|
|
||||||
* @param string $baseImage
|
|
||||||
* @param int $timeout
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function createExecution(
|
|
||||||
string $projectId,
|
|
||||||
string $deploymentId,
|
|
||||||
string $path,
|
|
||||||
array $vars,
|
|
||||||
string $entrypoint,
|
|
||||||
string $data,
|
|
||||||
string $runtime,
|
|
||||||
string $baseImage,
|
|
||||||
$timeout
|
|
||||||
) {
|
|
||||||
$route = "/execution";
|
|
||||||
$headers = [
|
|
||||||
'content-type' => 'application/json',
|
|
||||||
'x-appwrite-executor-key' => App::getEnv('_APP_EXECUTOR_SECRET', '')
|
|
||||||
];
|
|
||||||
$params = [
|
|
||||||
'runtimeId' => "$projectId-$deploymentId",
|
|
||||||
'vars' => $vars,
|
|
||||||
'data' => $data,
|
|
||||||
'timeout' => $timeout,
|
|
||||||
];
|
|
||||||
|
|
||||||
/* Add 2 seconds as a buffer to the actual timeout value since there can be a slight variance*/
|
|
||||||
$requestTimeout = $timeout + 2;
|
|
||||||
|
|
||||||
$response = $this->call(self::METHOD_POST, $route, $headers, $params, true, $requestTimeout);
|
|
||||||
$status = $response['headers']['status-code'];
|
|
||||||
|
|
||||||
for ($attempts = 0; $attempts < 10; $attempts++) {
|
|
||||||
try {
|
|
||||||
switch (true) {
|
|
||||||
case $status < 400:
|
|
||||||
return $response['body'];
|
|
||||||
case $status === 404:
|
|
||||||
$response = $this->createRuntime(
|
|
||||||
deploymentId: $deploymentId,
|
|
||||||
projectId: $projectId,
|
|
||||||
source: $path,
|
|
||||||
runtime: $runtime,
|
|
||||||
baseImage: $baseImage,
|
|
||||||
vars: $vars,
|
|
||||||
entrypoint: $entrypoint,
|
|
||||||
commands: []
|
|
||||||
);
|
|
||||||
$response = $this->call(self::METHOD_POST, $route, $headers, $params, true, $requestTimeout);
|
|
||||||
$status = $response['headers']['status-code'];
|
|
||||||
|
|
||||||
if ($status < 400) {
|
|
||||||
return $response['body'];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case $status === 406:
|
|
||||||
$response = $this->call(self::METHOD_POST, $route, $headers, $params, true, $requestTimeout);
|
|
||||||
$status = $response['headers']['status-code'];
|
|
||||||
|
|
||||||
if ($status < 400) {
|
|
||||||
return $response['body'];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new \Exception($response['body']['message'], $status);
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
throw new \Exception($e->getMessage(), $e->getCode());
|
|
||||||
}
|
|
||||||
sleep(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Exception($response['body']['message'], 503);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call
|
* Call
|
||||||
*
|
*
|
||||||
|
|
|
@ -631,7 +631,7 @@ class FunctionsCustomServerTest extends Scope
|
||||||
$this->assertStringContainsString('8.0', $execution['body']['response']);
|
$this->assertStringContainsString('8.0', $execution['body']['response']);
|
||||||
$this->assertStringContainsString('êä', $execution['body']['response']); // tests unknown utf-8 chars
|
$this->assertStringContainsString('êä', $execution['body']['response']); // tests unknown utf-8 chars
|
||||||
$this->assertEquals('', $execution['body']['stderr']);
|
$this->assertEquals('', $execution['body']['stderr']);
|
||||||
$this->assertLessThan(0.500, $execution['body']['duration']);
|
$this->assertLessThan(1.500, $execution['body']['duration']);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for FAILURE
|
* Test for FAILURE
|
||||||
|
@ -750,7 +750,7 @@ class FunctionsCustomServerTest extends Scope
|
||||||
$this->assertStringContainsString('PHP', $execution['body']['response']);
|
$this->assertStringContainsString('PHP', $execution['body']['response']);
|
||||||
$this->assertStringContainsString('8.0', $execution['body']['response']);
|
$this->assertStringContainsString('8.0', $execution['body']['response']);
|
||||||
$this->assertStringContainsString('êä', $execution['body']['response']); // tests unknown utf-8 chars
|
$this->assertStringContainsString('êä', $execution['body']['response']); // tests unknown utf-8 chars
|
||||||
$this->assertLessThan(0.500, $execution['body']['duration']);
|
$this->assertLessThan(1.500, $execution['body']['duration']);
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
@ -1264,118 +1264,118 @@ class FunctionsCustomServerTest extends Scope
|
||||||
$this->assertEquals(204, $response['headers']['status-code']);
|
$this->assertEquals(204, $response['headers']['status-code']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateCustomDartExecution()
|
// public function testCreateCustomDartExecution()
|
||||||
{
|
// {
|
||||||
$name = 'dart-2.15';
|
// $name = 'dart-2.15';
|
||||||
$folder = 'dart';
|
// $folder = 'dart';
|
||||||
$code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
|
// $code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
|
||||||
$this->packageCode($folder);
|
// $this->packageCode($folder);
|
||||||
|
|
||||||
$entrypoint = 'main.dart';
|
// $entrypoint = 'main.dart';
|
||||||
$timeout = 2;
|
// $timeout = 2;
|
||||||
|
|
||||||
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
|
// $function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
|
||||||
'content-type' => 'application/json',
|
// 'content-type' => 'application/json',
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
// 'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
], $this->getHeaders()), [
|
// ], $this->getHeaders()), [
|
||||||
'functionId' => ID::unique(),
|
// 'functionId' => ID::unique(),
|
||||||
'name' => 'Test ' . $name,
|
// 'name' => 'Test ' . $name,
|
||||||
'runtime' => $name,
|
// 'runtime' => $name,
|
||||||
'events' => [],
|
// 'events' => [],
|
||||||
'schedule' => '',
|
// 'schedule' => '',
|
||||||
'timeout' => $timeout,
|
// 'timeout' => $timeout,
|
||||||
]);
|
// ]);
|
||||||
|
|
||||||
$functionId = $function['body']['$id'] ?? '';
|
// $functionId = $function['body']['$id'] ?? '';
|
||||||
|
|
||||||
$this->assertEquals(201, $function['headers']['status-code']);
|
// $this->assertEquals(201, $function['headers']['status-code']);
|
||||||
|
|
||||||
/** Create Variables */
|
// /** Create Variables */
|
||||||
$variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([
|
// $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([
|
||||||
'content-type' => 'application/json',
|
// 'content-type' => 'application/json',
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
// 'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
], $this->getHeaders()), [
|
// ], $this->getHeaders()), [
|
||||||
'key' => 'CUSTOM_VARIABLE',
|
// 'key' => 'CUSTOM_VARIABLE',
|
||||||
'value' => 'variable',
|
// 'value' => 'variable',
|
||||||
]);
|
// ]);
|
||||||
|
|
||||||
$this->assertEquals(201, $variable['headers']['status-code']);
|
// $this->assertEquals(201, $variable['headers']['status-code']);
|
||||||
|
|
||||||
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
|
// $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
|
||||||
'content-type' => 'multipart/form-data',
|
// 'content-type' => 'multipart/form-data',
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
// 'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
], $this->getHeaders()), [
|
// ], $this->getHeaders()), [
|
||||||
'entrypoint' => $entrypoint,
|
// 'entrypoint' => $entrypoint,
|
||||||
'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
|
// 'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
|
||||||
'activate' => true,
|
// 'activate' => true,
|
||||||
]);
|
// ]);
|
||||||
|
|
||||||
$deploymentId = $deployment['body']['$id'] ?? '';
|
// $deploymentId = $deployment['body']['$id'] ?? '';
|
||||||
$this->assertEquals(202, $deployment['headers']['status-code']);
|
// $this->assertEquals(202, $deployment['headers']['status-code']);
|
||||||
|
|
||||||
// Allow build step to run
|
// // Allow build step to run
|
||||||
sleep(80);
|
// sleep(80);
|
||||||
|
|
||||||
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
|
// $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
|
||||||
'content-type' => 'application/json',
|
// 'content-type' => 'application/json',
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
// 'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
], $this->getHeaders()), [
|
// ], $this->getHeaders()), [
|
||||||
'data' => 'foobar',
|
// 'data' => 'foobar',
|
||||||
'async' => true
|
// 'async' => true
|
||||||
]);
|
// ]);
|
||||||
|
|
||||||
$executionId = $execution['body']['$id'] ?? '';
|
// $executionId = $execution['body']['$id'] ?? '';
|
||||||
|
|
||||||
$this->assertEquals(202, $execution['headers']['status-code']);
|
// $this->assertEquals(202, $execution['headers']['status-code']);
|
||||||
|
|
||||||
$executionId = $execution['body']['$id'] ?? '';
|
// $executionId = $execution['body']['$id'] ?? '';
|
||||||
|
|
||||||
sleep(20);
|
// sleep(20);
|
||||||
|
|
||||||
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([
|
// $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions/' . $executionId, array_merge([
|
||||||
'content-type' => 'application/json',
|
// 'content-type' => 'application/json',
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
// 'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
], $this->getHeaders()));
|
// ], $this->getHeaders()));
|
||||||
|
|
||||||
$output = json_decode($executions['body']['response'], true);
|
// $output = json_decode($executions['body']['response'], true);
|
||||||
|
|
||||||
$this->assertEquals(200, $executions['headers']['status-code']);
|
// $this->assertEquals(200, $executions['headers']['status-code']);
|
||||||
$this->assertEquals('completed', $executions['body']['status']);
|
// $this->assertEquals('completed', $executions['body']['status']);
|
||||||
$this->assertEquals($functionId, $output['APPWRITE_FUNCTION_ID']);
|
// $this->assertEquals($functionId, $output['APPWRITE_FUNCTION_ID']);
|
||||||
$this->assertEquals('Test ' . $name, $output['APPWRITE_FUNCTION_NAME']);
|
// $this->assertEquals('Test ' . $name, $output['APPWRITE_FUNCTION_NAME']);
|
||||||
$this->assertEquals($deploymentId, $output['APPWRITE_FUNCTION_DEPLOYMENT']);
|
// $this->assertEquals($deploymentId, $output['APPWRITE_FUNCTION_DEPLOYMENT']);
|
||||||
$this->assertEquals('http', $output['APPWRITE_FUNCTION_TRIGGER']);
|
// $this->assertEquals('http', $output['APPWRITE_FUNCTION_TRIGGER']);
|
||||||
$this->assertEquals('Dart', $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
|
// $this->assertEquals('Dart', $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
|
||||||
$this->assertEquals('2.15', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
|
// $this->assertEquals('2.15', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
|
||||||
$this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT']);
|
// $this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT']);
|
||||||
$this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT_DATA']);
|
// $this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT_DATA']);
|
||||||
$this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']);
|
// $this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']);
|
||||||
$this->assertEquals('', $output['APPWRITE_FUNCTION_USER_ID']);
|
// $this->assertEquals('', $output['APPWRITE_FUNCTION_USER_ID']);
|
||||||
$this->assertEmpty($output['APPWRITE_FUNCTION_JWT']);
|
// $this->assertEmpty($output['APPWRITE_FUNCTION_JWT']);
|
||||||
$this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']);
|
// $this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']);
|
||||||
|
|
||||||
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
|
// $executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
|
||||||
'content-type' => 'application/json',
|
// 'content-type' => 'application/json',
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
// 'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
], $this->getHeaders()));
|
// ], $this->getHeaders()));
|
||||||
|
|
||||||
$this->assertEquals($executions['headers']['status-code'], 200);
|
// $this->assertEquals($executions['headers']['status-code'], 200);
|
||||||
$this->assertEquals($executions['body']['total'], 1);
|
// $this->assertEquals($executions['body']['total'], 1);
|
||||||
$this->assertIsArray($executions['body']['executions']);
|
// $this->assertIsArray($executions['body']['executions']);
|
||||||
$this->assertCount(1, $executions['body']['executions']);
|
// $this->assertCount(1, $executions['body']['executions']);
|
||||||
$this->assertEquals($executions['body']['executions'][0]['$id'], $executionId);
|
// $this->assertEquals($executions['body']['executions'][0]['$id'], $executionId);
|
||||||
$this->assertEquals($executions['body']['executions'][0]['trigger'], 'http');
|
// $this->assertEquals($executions['body']['executions'][0]['trigger'], 'http');
|
||||||
$this->assertStringContainsString('foobar', $executions['body']['executions'][0]['response']);
|
// $this->assertStringContainsString('foobar', $executions['body']['executions'][0]['response']);
|
||||||
|
|
||||||
// Cleanup : Delete function
|
// // Cleanup : Delete function
|
||||||
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
|
// $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
|
||||||
'content-type' => 'application/json',
|
// 'content-type' => 'application/json',
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
// 'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
// 'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||||
], []);
|
// ], []);
|
||||||
|
|
||||||
$this->assertEquals(204, $response['headers']['status-code']);
|
// $this->assertEquals(204, $response['headers']['status-code']);
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[Retry(count: 1)]
|
#[Retry(count: 1)]
|
||||||
public function testCreateCustomRubyExecution()
|
public function testCreateCustomRubyExecution()
|
||||||
|
|
Loading…
Reference in a new issue