2020-05-05 01:34:31 +12:00
|
|
|
<?php
|
2020-07-17 00:04:06 +12:00
|
|
|
|
2020-12-08 06:43:39 +13:00
|
|
|
use Appwrite\Event\Event;
|
2021-06-30 23:36:58 +12:00
|
|
|
use Appwrite\Messaging\Adapter\Realtime;
|
2021-06-12 02:20:18 +12:00
|
|
|
use Appwrite\Resque\Worker;
|
2021-08-08 18:31:20 +12:00
|
|
|
use Appwrite\Stats\Stats;
|
2021-06-28 19:56:03 +12:00
|
|
|
use Appwrite\Utopia\Response\Model\Execution;
|
2021-01-17 12:38:13 +13:00
|
|
|
use Cron\CronExpression;
|
2020-11-03 19:53:32 +13:00
|
|
|
use Swoole\Runtime;
|
2020-07-20 02:43:59 +12:00
|
|
|
use Utopia\App;
|
2020-05-10 10:12:00 +12:00
|
|
|
use Utopia\CLI\Console;
|
2020-05-05 01:34:31 +12:00
|
|
|
use Utopia\Config\Config;
|
2021-05-05 09:45:41 +12:00
|
|
|
use Utopia\Database\Database;
|
|
|
|
use Utopia\Database\Document;
|
|
|
|
use Utopia\Database\Validator\Authorization;
|
2021-07-23 00:49:52 +12:00
|
|
|
use Utopia\Orchestration\Orchestration;
|
|
|
|
use Utopia\Orchestration\Adapter\DockerAPI;
|
|
|
|
use Utopia\Orchestration\Container;
|
2021-09-01 06:52:47 +12:00
|
|
|
use Utopia\Orchestration\Exception\Orchestration as OrchestrationException;
|
2021-08-13 21:00:20 +12:00
|
|
|
use Utopia\Orchestration\Exception\Timeout as TimeoutException;
|
2020-05-09 18:26:18 +12:00
|
|
|
|
2021-08-13 20:39:46 +12:00
|
|
|
require_once __DIR__.'/../init.php';
|
2020-05-10 04:39:50 +12:00
|
|
|
|
2021-04-17 04:50:16 +12:00
|
|
|
Runtime::enableCoroutine(0);
|
2020-05-10 04:39:50 +12:00
|
|
|
|
2021-04-15 01:07:26 +12:00
|
|
|
Console::title('Functions V1 Worker');
|
2021-09-01 21:09:04 +12:00
|
|
|
Console::success(APP_NAME . ' functions worker v1 has started');
|
2020-05-10 04:39:50 +12:00
|
|
|
|
2021-04-21 23:02:54 +12:00
|
|
|
$runtimes = Config::getParam('runtimes');
|
2020-05-09 18:26:18 +12:00
|
|
|
|
2021-07-23 00:49:52 +12:00
|
|
|
$dockerUser = App::getEnv('DOCKERHUB_PULL_USERNAME', null);
|
|
|
|
$dockerPass = App::getEnv('DOCKERHUB_PULL_PASSWORD', null);
|
2021-08-13 21:00:20 +12:00
|
|
|
$dockerEmail = App::getEnv('DOCKERHUB_PULL_EMAIL', null);
|
|
|
|
$orchestration = new Orchestration(new DockerAPI($dockerUser, $dockerPass, $dockerEmail));
|
2021-07-23 00:49:52 +12:00
|
|
|
|
2020-11-03 19:53:32 +13:00
|
|
|
/**
|
|
|
|
* Warmup Docker Images
|
|
|
|
*/
|
2020-07-20 18:43:25 +12:00
|
|
|
$warmupStart = \microtime(true);
|
2020-07-16 00:49:10 +12:00
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
Co\run(function () use ($runtimes, $orchestration) { // Warmup: make sure images are ready to run fast 🚀
|
|
|
|
foreach ($runtimes as $runtime) {
|
|
|
|
go(function () use ($runtime, $orchestration) {
|
|
|
|
Console::info('Warming up ' . $runtime['name'] . ' ' . $runtime['version'] . ' environment...');
|
2021-02-02 20:26:11 +13:00
|
|
|
|
2021-08-13 21:00:20 +12:00
|
|
|
$response = $orchestration->pull($runtime['image']);
|
2021-02-02 21:16:08 +13:00
|
|
|
|
2021-08-13 21:00:20 +12:00
|
|
|
if ($response) {
|
2021-07-23 00:49:52 +12:00
|
|
|
Console::success("Successfully Warmed up {$runtime['name']} {$runtime['version']}!");
|
2021-08-13 21:00:20 +12:00
|
|
|
} else {
|
|
|
|
Console::error("Failed to Warmup {$runtime['name']} {$runtime['version']}!");
|
2020-07-16 01:34:28 +12:00
|
|
|
}
|
2021-04-17 04:50:16 +12:00
|
|
|
});
|
2020-05-10 10:12:00 +12:00
|
|
|
}
|
2021-04-17 04:50:16 +12:00
|
|
|
});
|
2020-05-05 01:34:31 +12:00
|
|
|
|
2020-07-20 18:43:25 +12:00
|
|
|
$warmupEnd = \microtime(true);
|
2020-07-16 00:49:10 +12:00
|
|
|
$warmupTime = $warmupEnd - $warmupStart;
|
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
Console::success('Finished warmup in ' . $warmupTime . ' seconds');
|
2020-07-16 00:49:10 +12:00
|
|
|
|
2020-11-03 19:53:32 +13:00
|
|
|
/**
|
|
|
|
* List function servers
|
|
|
|
*/
|
|
|
|
$stdout = '';
|
|
|
|
$stderr = '';
|
|
|
|
|
|
|
|
$executionStart = \microtime(true);
|
|
|
|
|
2021-07-23 21:05:59 +12:00
|
|
|
$response = $orchestration->list(['label' => 'appwrite-type=function']);
|
2021-09-01 21:09:04 +12:00
|
|
|
/** @var Container[] $list */
|
2020-11-03 19:53:32 +13:00
|
|
|
$list = [];
|
|
|
|
|
2021-08-13 21:00:20 +12:00
|
|
|
foreach ($response as $value) {
|
2021-07-23 20:48:51 +12:00
|
|
|
$list[$value->getName()] = $value;
|
2021-08-03 22:17:22 +12:00
|
|
|
}
|
2020-11-03 19:53:32 +13:00
|
|
|
|
2021-07-23 00:49:52 +12:00
|
|
|
$executionEnd = \microtime(true);
|
2020-11-03 19:53:32 +13:00
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
Console::info(count($list) . ' functions listed in ' . ($executionEnd - $executionStart) . ' seconds');
|
2020-11-03 19:53:32 +13:00
|
|
|
|
2020-07-18 08:41:01 +12:00
|
|
|
/**
|
|
|
|
* 1. Get event args - DONE
|
|
|
|
* 2. Unpackage code in the isolated container - DONE
|
|
|
|
* 3. Execute in container with timeout
|
|
|
|
* + messure execution time - DONE
|
|
|
|
* + pass env vars - DONE
|
|
|
|
* + pass one-time api key
|
2020-07-20 18:43:25 +12:00
|
|
|
* 4. Update execution status - DONE
|
|
|
|
* 5. Update execution stdout & stderr - DONE
|
2020-07-23 17:52:03 +12:00
|
|
|
* 6. Trigger audit log - DONE
|
|
|
|
* 7. Trigger usage log - DONE
|
2020-07-18 08:41:01 +12:00
|
|
|
*/
|
|
|
|
|
2021-07-28 02:16:12 +12:00
|
|
|
// TODO avoid scheduled execution if delay is bigger than X offest
|
2020-07-18 08:41:01 +12:00
|
|
|
|
2021-06-12 02:20:18 +12:00
|
|
|
class FunctionsV1 extends Worker
|
2020-05-05 01:34:31 +12:00
|
|
|
{
|
2021-09-01 21:09:04 +12:00
|
|
|
public array $args = [];
|
2020-05-05 01:34:31 +12:00
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
public array $allowed = [];
|
2020-07-22 08:10:31 +12:00
|
|
|
|
2021-06-12 02:20:18 +12:00
|
|
|
public function init(): void
|
2020-05-05 01:34:31 +12:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2021-06-12 02:20:18 +12:00
|
|
|
public function run(): void
|
2020-05-05 01:34:31 +12:00
|
|
|
{
|
2020-11-03 19:53:32 +13:00
|
|
|
$projectId = $this->args['projectId'] ?? '';
|
|
|
|
$functionId = $this->args['functionId'] ?? '';
|
2021-05-17 23:32:37 +12:00
|
|
|
$webhooks = $this->args['webhooks'] ?? [];
|
2020-11-03 19:53:32 +13:00
|
|
|
$executionId = $this->args['executionId'] ?? '';
|
|
|
|
$trigger = $this->args['trigger'] ?? '';
|
|
|
|
$event = $this->args['event'] ?? '';
|
2021-01-17 12:38:13 +13:00
|
|
|
$scheduleOriginal = $this->args['scheduleOriginal'] ?? '';
|
2021-03-31 00:42:58 +13:00
|
|
|
$eventData = (!empty($this->args['eventData'])) ? json_encode($this->args['eventData']) : '';
|
2021-03-10 08:58:03 +13:00
|
|
|
$data = $this->args['data'] ?? '';
|
2021-03-11 05:58:46 +13:00
|
|
|
$userId = $this->args['userId'] ?? '';
|
|
|
|
$jwt = $this->args['jwt'] ?? '';
|
2020-07-16 08:29:55 +12:00
|
|
|
|
2021-07-26 11:14:19 +12:00
|
|
|
$database = $this->getInternalDB($projectId);
|
2020-07-17 00:04:06 +12:00
|
|
|
|
2020-08-04 17:23:38 +12:00
|
|
|
switch ($trigger) {
|
|
|
|
case 'event':
|
2020-08-05 08:09:01 +12:00
|
|
|
$limit = 30;
|
|
|
|
$sum = 30;
|
|
|
|
$offset = 0;
|
2021-09-01 21:09:04 +12:00
|
|
|
$functions = [];
|
|
|
|
/** @var Document[] $functions */
|
2020-08-05 08:09:01 +12:00
|
|
|
|
|
|
|
while ($sum >= $limit) {
|
|
|
|
|
|
|
|
Authorization::disable();
|
|
|
|
|
2021-05-05 09:45:41 +12:00
|
|
|
$functions = $database->find('functions', [], $limit, $offset, ['name'], [Database::ORDER_ASC]);
|
2020-08-05 08:09:01 +12:00
|
|
|
|
|
|
|
Authorization::reset();
|
|
|
|
|
|
|
|
$sum = \count($functions);
|
|
|
|
$offset = $offset + $limit;
|
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
Console::log('Fetched ' . $sum . ' functions...');
|
2020-08-05 17:18:45 +12:00
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
foreach ($functions as $function) {
|
2020-08-05 17:18:45 +12:00
|
|
|
$events = $function->getAttribute('events', []);
|
|
|
|
$tag = $function->getAttribute('tag', []);
|
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
Console::success('Itterating function: ' . $function->getAttribute('name'));
|
2020-08-05 17:18:45 +12:00
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
if (!\in_array($event, $events) || empty($tag)) {
|
2020-08-05 17:18:45 +12:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
Console::success('Triggered function: ' . $event);
|
|
|
|
|
|
|
|
$this->execute(
|
|
|
|
trigger: 'event',
|
|
|
|
projectId: $projectId,
|
|
|
|
executionId: '',
|
|
|
|
database: $database,
|
|
|
|
function: $function,
|
|
|
|
event: $event,
|
|
|
|
eventData: $eventData,
|
|
|
|
data: $data,
|
|
|
|
webhooks: $webhooks,
|
|
|
|
userId: $userId,
|
|
|
|
jwt: $jwt
|
|
|
|
);
|
2020-08-05 17:18:45 +12:00
|
|
|
}
|
2020-08-05 08:09:01 +12:00
|
|
|
}
|
2020-08-04 17:23:38 +12:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'schedule':
|
2020-10-02 21:25:57 +13:00
|
|
|
/*
|
2020-11-03 19:53:32 +13:00
|
|
|
* 1. Get Original Task
|
|
|
|
* 2. Check for updates
|
|
|
|
* If has updates skip task and don't reschedule
|
|
|
|
* If status not equal to play skip task
|
|
|
|
* 3. Check next run date, update task and add new job at the given date
|
|
|
|
* 4. Execute task (set optional timeout)
|
|
|
|
* 5. Update task response to log
|
|
|
|
* On success reset error count
|
|
|
|
* On failure add error count
|
|
|
|
* If error count bigger than allowed change status to pause
|
|
|
|
*/
|
2021-01-17 12:38:13 +13:00
|
|
|
|
|
|
|
// Reschedule
|
|
|
|
Authorization::disable();
|
2021-05-05 09:45:41 +12:00
|
|
|
$function = $database->getDocument('functions', $functionId);
|
2021-01-17 12:38:13 +13:00
|
|
|
Authorization::reset();
|
|
|
|
|
2021-05-05 09:45:41 +12:00
|
|
|
if (empty($function->getId())) {
|
2021-01-17 12:38:13 +13:00
|
|
|
throw new Exception('Function not found ('.$functionId.')');
|
|
|
|
}
|
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
if ($scheduleOriginal && $scheduleOriginal !== $function->getAttribute('schedule')) { // Schedule has changed from previous run, ignore this run.
|
2021-01-17 12:38:13 +13:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-02-22 10:37:22 +13:00
|
|
|
$cron = new CronExpression($function->getAttribute('schedule'));
|
2021-01-17 12:38:13 +13:00
|
|
|
$next = (int) $cron->getNextRunDate()->format('U');
|
|
|
|
|
|
|
|
$function
|
|
|
|
->setAttribute('scheduleNext', $next)
|
2021-09-01 21:09:04 +12:00
|
|
|
->setAttribute('schedulePrevious', \time());
|
2021-01-17 12:38:13 +13:00
|
|
|
|
2021-01-17 13:07:43 +13:00
|
|
|
Authorization::disable();
|
|
|
|
|
2021-05-05 09:45:41 +12:00
|
|
|
$function = $database->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
|
|
|
|
'scheduleNext' => (int)$next,
|
|
|
|
])));
|
2021-01-17 12:38:13 +13:00
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
if ($function === false) {
|
|
|
|
throw new Exception('Function update failed (' . $functionId . ')');
|
|
|
|
}
|
|
|
|
|
2021-01-17 13:07:43 +13:00
|
|
|
Authorization::reset();
|
|
|
|
|
2021-01-17 12:38:13 +13:00
|
|
|
ResqueScheduler::enqueueAt($next, 'v1-functions', 'FunctionsV1', [
|
|
|
|
'projectId' => $projectId,
|
2021-05-18 03:52:22 +12:00
|
|
|
'webhooks' => $webhooks,
|
2021-01-17 12:38:13 +13:00
|
|
|
'functionId' => $function->getId(),
|
|
|
|
'executionId' => null,
|
|
|
|
'trigger' => 'schedule',
|
|
|
|
'scheduleOriginal' => $function->getAttribute('schedule', ''),
|
|
|
|
]); // Async task rescheduale
|
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
$this->execute(
|
|
|
|
trigger: $trigger,
|
|
|
|
projectId: $projectId,
|
|
|
|
executionId: $executionId,
|
|
|
|
database: $database,
|
|
|
|
function: $function,
|
|
|
|
data: $data,
|
|
|
|
webhooks: $webhooks,
|
|
|
|
userId: $userId,
|
|
|
|
jwt: $jwt
|
|
|
|
);
|
2020-08-04 17:23:38 +12:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'http':
|
|
|
|
Authorization::disable();
|
2021-05-05 09:45:41 +12:00
|
|
|
$function = $database->getDocument('functions', $functionId);
|
2020-08-04 17:23:38 +12:00
|
|
|
Authorization::reset();
|
|
|
|
|
2021-05-05 09:45:41 +12:00
|
|
|
if (empty($function->getId())) {
|
2020-12-11 03:35:39 +13:00
|
|
|
throw new Exception('Function not found ('.$functionId.')');
|
2020-08-04 17:23:38 +12:00
|
|
|
}
|
2020-07-17 00:04:06 +12:00
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
$this->execute(
|
|
|
|
trigger: $trigger,
|
|
|
|
projectId: $projectId,
|
|
|
|
executionId: $executionId,
|
|
|
|
database: $database,
|
|
|
|
function: $function,
|
|
|
|
data: $data,
|
|
|
|
webhooks: $webhooks,
|
|
|
|
userId: $userId,
|
|
|
|
jwt: $jwt
|
|
|
|
);
|
2020-08-04 17:23:38 +12:00
|
|
|
break;
|
2020-07-17 00:04:06 +12:00
|
|
|
}
|
2020-08-04 17:23:38 +12:00
|
|
|
}
|
|
|
|
|
2020-11-03 19:53:32 +13:00
|
|
|
/**
|
|
|
|
* Execute function tag
|
|
|
|
*
|
|
|
|
* @param string $trigger
|
|
|
|
* @param string $projectId
|
|
|
|
* @param string $executionId
|
|
|
|
* @param Database $database
|
2021-09-01 21:09:04 +12:00
|
|
|
* @param Document $function
|
2020-11-03 19:53:32 +13:00
|
|
|
* @param string $event
|
2021-03-25 02:36:36 +13:00
|
|
|
* @param string $eventData
|
2021-03-10 08:58:03 +13:00
|
|
|
* @param string $data
|
2021-05-18 03:52:22 +12:00
|
|
|
* @param array $webhooks
|
|
|
|
* @param string $userId
|
|
|
|
* @param string $jwt
|
2020-11-03 19:53:32 +13:00
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2021-05-17 23:32:37 +12:00
|
|
|
public function execute(string $trigger, string $projectId, string $executionId, Database $database, Document $function, string $event = '', string $eventData = '', string $data = '', array $webhooks = [], string $userId = '', string $jwt = ''): void
|
2020-08-04 17:23:38 +12:00
|
|
|
{
|
2021-09-30 23:32:10 +13:00
|
|
|
global $list, $register, $orchestration;
|
2020-08-05 08:09:01 +12:00
|
|
|
|
2021-04-21 23:02:54 +12:00
|
|
|
$runtimes = Config::getParam('runtimes');
|
2020-07-17 00:04:06 +12:00
|
|
|
|
|
|
|
Authorization::disable();
|
2021-05-05 09:45:41 +12:00
|
|
|
$tag = $database->getDocument('tags', $function->getAttribute('tag', ''));
|
2020-07-17 00:04:06 +12:00
|
|
|
Authorization::reset();
|
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
if ($tag->getAttribute('functionId') !== $function->getId()) {
|
2020-07-17 00:04:06 +12:00
|
|
|
throw new Exception('Tag not found', 404);
|
|
|
|
}
|
|
|
|
|
2020-07-17 09:51:26 +12:00
|
|
|
Authorization::disable();
|
2020-07-18 08:41:01 +12:00
|
|
|
|
2021-05-05 09:45:41 +12:00
|
|
|
$execution = (!empty($executionId)) ? $database->getDocument('executions', $executionId) : $database->createDocument('executions', new Document([
|
|
|
|
'$read' => [],
|
|
|
|
'$write' => [],
|
2020-08-05 17:18:45 +12:00
|
|
|
'dateCreated' => time(),
|
|
|
|
'functionId' => $function->getId(),
|
|
|
|
'trigger' => $trigger, // http / schedule / event
|
|
|
|
'status' => 'processing', // waiting / processing / completed / failed
|
|
|
|
'exitCode' => 0,
|
|
|
|
'stdout' => '',
|
|
|
|
'stderr' => '',
|
|
|
|
'time' => 0,
|
2021-05-05 09:45:41 +12:00
|
|
|
]));
|
2020-08-05 17:18:45 +12:00
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
if ($execution->isEmpty()) {
|
2021-01-20 17:49:00 +13:00
|
|
|
throw new Exception('Failed to create or read execution');
|
2020-07-17 09:51:26 +12:00
|
|
|
}
|
2021-09-01 21:09:04 +12:00
|
|
|
|
2020-07-17 09:51:26 +12:00
|
|
|
Authorization::reset();
|
|
|
|
|
2021-06-23 03:56:05 +12:00
|
|
|
$runtime = (isset($runtimes[$function->getAttribute('runtime', '')]))
|
|
|
|
? $runtimes[$function->getAttribute('runtime', '')]
|
2020-07-17 00:04:06 +12:00
|
|
|
: null;
|
2020-07-17 03:20:51 +12:00
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
if (\is_null($runtime)) {
|
|
|
|
throw new Exception('Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
|
2020-07-16 08:29:55 +12:00
|
|
|
}
|
|
|
|
|
2020-07-20 18:43:25 +12:00
|
|
|
$vars = \array_merge($function->getAttribute('vars', []), [
|
2020-08-04 17:23:38 +12:00
|
|
|
'APPWRITE_FUNCTION_ID' => $function->getId(),
|
2020-07-20 15:59:33 +12:00
|
|
|
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name', ''),
|
2020-08-04 17:23:38 +12:00
|
|
|
'APPWRITE_FUNCTION_TAG' => $tag->getId(),
|
|
|
|
'APPWRITE_FUNCTION_TRIGGER' => $trigger,
|
2021-04-30 03:08:59 +12:00
|
|
|
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'],
|
|
|
|
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'],
|
2020-12-09 11:23:15 +13:00
|
|
|
'APPWRITE_FUNCTION_EVENT' => $event,
|
2021-03-25 02:36:36 +13:00
|
|
|
'APPWRITE_FUNCTION_EVENT_DATA' => $eventData,
|
2021-03-10 08:58:03 +13:00
|
|
|
'APPWRITE_FUNCTION_DATA' => $data,
|
2021-03-20 04:46:03 +13:00
|
|
|
'APPWRITE_FUNCTION_USER_ID' => $userId,
|
2021-03-11 05:58:46 +13:00
|
|
|
'APPWRITE_FUNCTION_JWT' => $jwt,
|
2021-03-20 04:46:03 +13:00
|
|
|
'APPWRITE_FUNCTION_PROJECT_ID' => $projectId,
|
2020-07-17 03:20:51 +12:00
|
|
|
]);
|
2021-09-01 21:09:04 +12:00
|
|
|
$tagId = $tag->getId() ?? '';
|
2020-08-08 22:41:17 +12:00
|
|
|
$tagPath = $tag->getAttribute('path', '');
|
2021-09-01 21:09:04 +12:00
|
|
|
$tagPathTarget = '/tmp/project-' . $projectId . '/' . $tagId . '/code.tar.gz';
|
2020-07-17 00:04:06 +12:00
|
|
|
$tagPathTargetDir = \pathinfo($tagPathTarget, PATHINFO_DIRNAME);
|
2021-09-01 21:09:04 +12:00
|
|
|
$container = 'appwrite-function-' . $tagId;
|
2020-07-20 18:43:25 +12:00
|
|
|
$command = \escapeshellcmd($tag->getAttribute('command', ''));
|
2020-07-17 00:04:06 +12:00
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
if (!\is_readable($tagPath)) {
|
|
|
|
throw new Exception('Code is not readable: ' . $tag->getAttribute('path', ''));
|
2020-07-17 00:04:06 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!\file_exists($tagPathTargetDir)) {
|
|
|
|
if (!\mkdir($tagPathTargetDir, 0755, true)) {
|
2021-09-01 21:09:04 +12:00
|
|
|
throw new Exception('Can\'t create directory ' . $tagPathTargetDir);
|
2020-07-17 00:04:06 +12:00
|
|
|
}
|
|
|
|
}
|
2021-09-01 21:09:04 +12:00
|
|
|
|
2020-07-17 00:04:06 +12:00
|
|
|
if (!\file_exists($tagPathTarget)) {
|
2021-09-01 21:09:04 +12:00
|
|
|
if (!\copy($tagPath, $tagPathTarget)) {
|
|
|
|
throw new Exception('Can\'t create temporary code file ' . $tagPathTarget);
|
2020-07-17 00:04:06 +12:00
|
|
|
}
|
|
|
|
}
|
2020-07-17 22:30:08 +12:00
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
if (isset($list[$container]) && !(\substr($list[$container]->getStatus(), 0, 2) === 'Up')) { // Remove conatiner if not online
|
2020-07-18 08:41:01 +12:00
|
|
|
$stdout = '';
|
|
|
|
$stderr = '';
|
2021-07-23 00:49:52 +12:00
|
|
|
|
2021-08-14 02:16:05 +12:00
|
|
|
try {
|
|
|
|
$orchestration->remove($container);
|
|
|
|
} catch (Exception $e) {
|
2021-09-01 21:09:04 +12:00
|
|
|
Console::warning('Failed to remove container: ' . $e->getMessage());
|
2020-07-18 08:41:01 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
unset($list[$container]);
|
|
|
|
}
|
|
|
|
|
2021-01-15 19:02:48 +13:00
|
|
|
/**
|
|
|
|
* Limit CPU Usage - DONE
|
|
|
|
* Limit Memory Usage - DONE
|
|
|
|
* Limit Network Usage
|
|
|
|
* Limit Storage Usage (//--storage-opt size=120m \)
|
|
|
|
* Make sure no access to redis, mariadb, influxdb or other system services
|
|
|
|
* Make sure no access to NFS server / storage volumes
|
|
|
|
* Access Appwrite REST from internal network for improved performance
|
|
|
|
*/
|
2021-09-01 21:09:04 +12:00
|
|
|
if (!isset($list[$container])) { // Create contianer if not ready
|
2020-07-18 08:41:01 +12:00
|
|
|
$stdout = '';
|
|
|
|
$stderr = '';
|
2021-09-01 21:09:04 +12:00
|
|
|
|
2020-07-18 08:41:01 +12:00
|
|
|
$executionStart = \microtime(true);
|
2020-11-03 19:53:32 +13:00
|
|
|
$executionTime = \time();
|
|
|
|
|
2021-09-03 06:59:58 +12:00
|
|
|
$orchestration->setCpus((int) App::getEnv('_APP_FUNCTIONS_CPUS', 0));
|
|
|
|
$orchestration->setMemory((int) App::getEnv('_APP_FUNCTIONS_MEMORY', 256));
|
|
|
|
$orchestration->setSwap((int) App::getEnv('_APP_FUNCTIONS_MEMORY_SWAP', 256));
|
2021-07-23 00:49:52 +12:00
|
|
|
|
2021-09-03 23:41:42 +12:00
|
|
|
foreach ($vars as $key => $value) {
|
|
|
|
$vars[$key] = strval($value);
|
2020-07-18 08:41:01 +12:00
|
|
|
}
|
2021-07-22 22:08:08 +12:00
|
|
|
|
2021-07-23 01:53:30 +12:00
|
|
|
$id = $orchestration->run(
|
2021-08-04 22:19:20 +12:00
|
|
|
image: $runtime['image'],
|
|
|
|
name: $container,
|
2021-09-01 21:09:04 +12:00
|
|
|
command: [
|
|
|
|
'tail',
|
|
|
|
'-f',
|
|
|
|
'/dev/null'
|
2021-08-03 22:17:22 +12:00
|
|
|
],
|
2021-08-12 00:44:31 +12:00
|
|
|
entrypoint: '',
|
2021-08-04 22:19:20 +12:00
|
|
|
workdir: '/usr/local/src',
|
|
|
|
volumes: [],
|
|
|
|
vars: $vars,
|
|
|
|
mountFolder: $tagPathTargetDir,
|
|
|
|
labels: [
|
2021-08-03 22:17:22 +12:00
|
|
|
'appwrite-type' => 'function',
|
|
|
|
'appwrite-created' => strval($executionTime)
|
2021-09-01 21:09:04 +12:00
|
|
|
]
|
|
|
|
);
|
2020-07-20 02:43:59 +12:00
|
|
|
|
2021-08-13 21:00:20 +12:00
|
|
|
$untarStdout = '';
|
|
|
|
$untarStderr = '';
|
2021-07-23 21:28:32 +12:00
|
|
|
|
2021-08-04 22:19:20 +12:00
|
|
|
$untarSuccess = $orchestration->execute(
|
2021-09-01 21:09:04 +12:00
|
|
|
name: $container,
|
2021-08-04 22:19:20 +12:00
|
|
|
command: [
|
2021-07-23 21:28:32 +12:00
|
|
|
'sh',
|
|
|
|
'-c',
|
2021-08-03 22:17:22 +12:00
|
|
|
'mv /tmp/code.tar.gz /usr/local/src/code.tar.gz && tar -zxf /usr/local/src/code.tar.gz --strip 1 && rm /usr/local/src/code.tar.gz'
|
|
|
|
],
|
2021-09-01 21:09:04 +12:00
|
|
|
stdout: $untarStdout,
|
2021-08-13 21:00:20 +12:00
|
|
|
stderr: $untarStderr,
|
2021-08-04 22:19:20 +12:00
|
|
|
vars: $vars,
|
2021-09-01 21:09:04 +12:00
|
|
|
timeout: 60
|
|
|
|
);
|
2021-07-22 22:08:08 +12:00
|
|
|
|
2021-07-23 21:28:32 +12:00
|
|
|
if (!$untarSuccess) {
|
2021-09-01 21:09:04 +12:00
|
|
|
throw new Exception('Failed to extract tar: ' . $untarStderr);
|
2021-07-22 22:08:08 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
$executionEnd = \microtime(true);
|
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
$list[$container] = new Container(
|
|
|
|
$container,
|
|
|
|
$id,
|
|
|
|
'Up',
|
2021-08-03 22:17:22 +12:00
|
|
|
[
|
2020-11-03 19:53:32 +13:00
|
|
|
'appwrite-type' => 'function',
|
2021-08-03 22:17:22 +12:00
|
|
|
'appwrite-created' => strval($executionTime),
|
2021-09-01 21:09:04 +12:00
|
|
|
]
|
|
|
|
);
|
2020-11-03 19:53:32 +13:00
|
|
|
|
2021-08-03 22:17:22 +12:00
|
|
|
Console::info('Function created in ' . ($executionEnd - $executionStart) . ' seconds');
|
2021-09-01 21:09:04 +12:00
|
|
|
} else {
|
2020-07-18 08:41:01 +12:00
|
|
|
Console::info('Container is ready to run');
|
|
|
|
}
|
2021-07-14 03:18:02 +12:00
|
|
|
|
2020-07-18 08:41:01 +12:00
|
|
|
$stdout = '';
|
|
|
|
$stderr = '';
|
|
|
|
|
|
|
|
$executionStart = \microtime(true);
|
2021-07-14 03:18:02 +12:00
|
|
|
|
2021-07-23 00:49:52 +12:00
|
|
|
$exitCode = 0;
|
|
|
|
|
|
|
|
try {
|
2021-09-09 23:06:00 +12:00
|
|
|
$exitCode = (int) !$orchestration->execute(
|
2021-09-01 21:09:04 +12:00
|
|
|
name: $container,
|
|
|
|
command: $orchestration->parseCommandString($command),
|
|
|
|
stdout: $stdout,
|
|
|
|
stderr: $stderr,
|
|
|
|
vars: $vars,
|
|
|
|
timeout: $function->getAttribute('timeout', (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900))
|
|
|
|
);
|
2021-07-23 00:49:52 +12:00
|
|
|
} catch (TimeoutException $e) {
|
2021-08-12 01:23:37 +12:00
|
|
|
$exitCode = 124;
|
2021-09-01 06:52:47 +12:00
|
|
|
} catch (OrchestrationException $e) {
|
|
|
|
$stderr = $e->getMessage();
|
|
|
|
$exitCode = 1;
|
2021-07-23 00:49:52 +12:00
|
|
|
}
|
2020-07-18 08:41:01 +12:00
|
|
|
|
|
|
|
$executionEnd = \microtime(true);
|
2020-07-21 22:33:23 +12:00
|
|
|
$executionTime = ($executionEnd - $executionStart);
|
2020-07-24 02:59:44 +12:00
|
|
|
$functionStatus = ($exitCode === 0) ? 'completed' : 'failed';
|
2020-07-18 08:41:01 +12:00
|
|
|
|
2021-08-03 22:17:22 +12:00
|
|
|
Console::info('Function executed in ' . ($executionEnd - $executionStart) . ' seconds, status: ' . $functionStatus);
|
2020-07-17 00:04:06 +12:00
|
|
|
|
2021-10-08 21:39:37 +13:00
|
|
|
$execution = Authorization::skip(function() use ($database, $execution, $tag, $functionStatus, $exitCode, $stdout, $stderr, $executionTime) {
|
|
|
|
return $database->updateDocument('executions', $execution->getId(), new Document(array_merge($execution->getArrayCopy(), [
|
|
|
|
'tagId' => $tag->getId(),
|
|
|
|
'status' => $functionStatus,
|
|
|
|
'exitCode' => $exitCode,
|
|
|
|
'stdout' => \utf8_encode(\mb_substr($stdout, -8000)), // log last 8000 chars output
|
|
|
|
'stderr' => \utf8_encode(\mb_substr($stderr, -8000)), // log last 8000 chars output
|
|
|
|
'time' => (float)$executionTime,
|
|
|
|
])));
|
|
|
|
});
|
2020-07-17 09:51:26 +12:00
|
|
|
|
2021-06-28 19:56:03 +12:00
|
|
|
$executionModel = new Execution();
|
2021-03-13 07:28:29 +13:00
|
|
|
$executionUpdate = new Event('v1-webhooks', 'WebhooksV1');
|
|
|
|
|
|
|
|
$executionUpdate
|
|
|
|
->setParam('projectId', $projectId)
|
2021-03-13 08:01:58 +13:00
|
|
|
->setParam('userId', $userId)
|
2021-05-17 23:32:37 +12:00
|
|
|
->setParam('webhooks', $webhooks)
|
2021-03-13 07:28:29 +13:00
|
|
|
->setParam('event', 'functions.executions.update')
|
2021-06-28 19:56:03 +12:00
|
|
|
->setParam('eventData', $execution->getArrayCopy(array_keys($executionModel->getRules())));
|
2021-03-13 07:28:29 +13:00
|
|
|
|
|
|
|
$executionUpdate->trigger();
|
|
|
|
|
2021-06-30 23:36:58 +12:00
|
|
|
$target = Realtime::fromPayload('functions.executions.update', $execution);
|
2021-03-25 05:53:44 +13:00
|
|
|
|
2021-06-30 23:36:58 +12:00
|
|
|
Realtime::send(
|
2021-09-09 23:06:00 +12:00
|
|
|
projectId: $projectId,
|
|
|
|
payload: $execution->getArrayCopy(),
|
|
|
|
event: 'functions.executions.update',
|
|
|
|
channels: $target['channels'],
|
|
|
|
roles: $target['roles']
|
2021-06-30 23:36:58 +12:00
|
|
|
);
|
2021-03-25 05:53:44 +13:00
|
|
|
|
2021-01-15 18:30:49 +13:00
|
|
|
if(App::getEnv('_APP_USAGE_STATS', 'enabled') == 'enabled') {
|
2021-08-02 18:56:12 +12:00
|
|
|
$statsd = $register->get('statsd');
|
|
|
|
|
2021-08-08 18:31:20 +12:00
|
|
|
$usage = new Stats($statsd);
|
2021-08-08 17:36:08 +12:00
|
|
|
|
|
|
|
$usage
|
|
|
|
->setParam('projectId', $projectId)
|
|
|
|
->setParam('functionId', $function->getId())
|
|
|
|
->setParam('functionExecution', 1)
|
|
|
|
->setParam('functionStatus', $functionStatus)
|
|
|
|
->setParam('functionExecutionTime', $executionTime * 1000) // ms
|
|
|
|
->setParam('networkRequestSize', 0)
|
|
|
|
->setParam('networkResponseSize', 0)
|
2021-08-08 18:31:20 +12:00
|
|
|
->submit()
|
2021-08-08 17:36:08 +12:00
|
|
|
;
|
2021-09-30 23:32:10 +13:00
|
|
|
|
|
|
|
$usage->submit();
|
2021-01-13 18:57:15 +13:00
|
|
|
}
|
2020-07-21 22:33:23 +12:00
|
|
|
|
2020-11-03 19:53:32 +13:00
|
|
|
$this->cleanup();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cleanup any hanging containers above the allowed max containers.
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function cleanup(): void
|
|
|
|
{
|
2021-09-01 21:09:04 +12:00
|
|
|
/** @var Container[] $list */
|
2020-11-03 19:53:32 +13:00
|
|
|
global $list;
|
2021-09-01 21:09:04 +12:00
|
|
|
/** @var Orchestration $orchestration */
|
2021-07-23 00:49:52 +12:00
|
|
|
global $orchestration;
|
2020-11-03 19:53:32 +13:00
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
Console::success(count($list) . ' running containers counted');
|
2020-07-17 03:20:51 +12:00
|
|
|
|
2020-07-20 02:43:59 +12:00
|
|
|
$max = (int) App::getEnv('_APP_FUNCTIONS_CONTAINERS');
|
2020-07-18 08:41:01 +12:00
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
if (\count($list) > $max) {
|
2020-07-18 08:41:01 +12:00
|
|
|
Console::info('Starting containers cleanup');
|
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
\uasort($list, function (Container $item1, Container $item2) {
|
2021-07-23 21:05:59 +12:00
|
|
|
return (int)($item1->getLabels['appwrite-created'] ?? 0) <=> (int)($item2->getLabels['appwrite-created'] ?? 0);
|
2020-07-18 08:41:01 +12:00
|
|
|
});
|
|
|
|
|
2021-09-01 21:09:04 +12:00
|
|
|
while (\count($list) > $max) {
|
2020-12-30 23:44:33 +13:00
|
|
|
$first = \array_shift($list);
|
2020-07-18 08:41:01 +12:00
|
|
|
|
2021-07-23 00:49:52 +12:00
|
|
|
try {
|
2021-07-23 21:05:59 +12:00
|
|
|
$orchestration->remove($first->getName(), true);
|
2021-09-01 21:09:04 +12:00
|
|
|
Console::info('Removed container: ' . $first->getName());
|
2021-07-23 00:49:52 +12:00
|
|
|
} catch (Exception $e) {
|
2021-09-01 21:09:04 +12:00
|
|
|
Console::error('Failed to remove container: ' . $e);
|
2020-12-30 23:44:33 +13:00
|
|
|
}
|
2020-07-18 08:41:01 +12:00
|
|
|
}
|
|
|
|
}
|
2020-05-05 01:34:31 +12:00
|
|
|
}
|
|
|
|
|
2020-11-03 19:53:32 +13:00
|
|
|
/**
|
|
|
|
* Filter ENV vars
|
|
|
|
*
|
|
|
|
* @param string $string
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
2020-07-22 08:10:31 +12:00
|
|
|
public function filterEnvKey(string $string): string
|
|
|
|
{
|
2021-09-01 21:09:04 +12:00
|
|
|
if (empty($this->allowed)) {
|
2020-08-05 08:09:01 +12:00
|
|
|
$this->allowed = array_fill_keys(\str_split('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_'), true);
|
2020-07-22 08:10:31 +12:00
|
|
|
}
|
|
|
|
|
2020-08-05 08:09:01 +12:00
|
|
|
$string = \str_split($string);
|
2020-07-22 08:10:31 +12:00
|
|
|
$output = '';
|
|
|
|
|
|
|
|
foreach ($string as $char) {
|
2021-09-01 21:09:04 +12:00
|
|
|
if (\array_key_exists($char, $this->allowed)) {
|
2020-07-22 08:10:31 +12:00
|
|
|
$output .= $char;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
|
2021-06-12 02:20:18 +12:00
|
|
|
public function shutdown(): void
|
2020-05-05 01:34:31 +12:00
|
|
|
{
|
|
|
|
}
|
2021-09-01 21:09:04 +12:00
|
|
|
}
|