Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions app/config/collections.php
Original file line number Diff line number Diff line change
Expand Up @@ -2386,6 +2386,17 @@
'array' => false,
'filters' => [],
],
[
'$id' => 'stdout',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 1000000,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'statusCode',
'type' => Database::VAR_INTEGER,
Expand Down
2 changes: 1 addition & 1 deletion app/config/runtimes.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Utopia\App;
use Appwrite\Runtimes\Runtimes;

$runtimes = new Runtimes('v1');
$runtimes = new Runtimes('v2');

$allowList = empty(App::getEnv('_APP_FUNCTIONS_RUNTIMES')) ? [] : \explode(',', App::getEnv('_APP_FUNCTIONS_RUNTIMES'));

Expand Down
28 changes: 28 additions & 0 deletions app/controllers/api/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,7 @@
$execution->setAttribute('status', $executionResponse['status']);
$execution->setAttribute('statusCode', $executionResponse['statusCode']);
$execution->setAttribute('response', $executionResponse['response']);
$execution->setAttribute('stdout', $executionResponse['stdout']);
$execution->setAttribute('stderr', $executionResponse['stderr']);
$execution->setAttribute('time', $executionResponse['time']);
} catch (\Throwable $th) {
Expand All @@ -938,6 +939,14 @@
->setParam('functionStatus', $execution->getAttribute('status', ''))
->setParam('functionExecutionTime', $execution->getAttribute('time') * 1000); // ms

$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
if (!$isPrivilegedUser && !$isAppUser) {
$execution->setAttribute('stdout', '');
$execution->setAttribute('stderr', '');
}

$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($execution, Response::MODEL_EXECUTION);
Expand Down Expand Up @@ -989,6 +998,17 @@
$results = $dbForProject->find('executions', $queries, $limit, $offset, [], [Database::ORDER_DESC], $cursorExecution ?? null, $cursorDirection);
$total = $dbForProject->count('executions', $queries, APP_LIMIT_COUNT);

$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
if (!$isPrivilegedUser && !$isAppUser) {
$results = array_map(function ($execution) {
$execution->setAttribute('stdout', '');
$execution->setAttribute('stderr', '');
return $execution;
}, $results);
}

$response->dynamic(new Document([
'executions' => $results,
'total' => $total,
Expand Down Expand Up @@ -1028,6 +1048,14 @@
throw new Exception(Exception::EXECUTION_NOT_FOUND);
}

$roles = Authorization::getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
if (!$isPrivilegedUser && !$isAppUser) {
$execution->setAttribute('stdout', '');
$execution->setAttribute('stderr', '');
}

$response->dynamic($execution, Response::MODEL_EXECUTION);
});

Expand Down
19 changes: 14 additions & 5 deletions app/executor.php
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,7 @@ function (string $runtimeId, array $vars, string $data, $timeout, $activeRuntime
$executionStart = \microtime(true);
$stdout = '';
$stderr = '';
$res = '';
$statusCode = 0;
$errNo = -1;
$executorResponse = '';
Expand Down Expand Up @@ -515,6 +516,7 @@ function (string $runtimeId, array $vars, string $data, $timeout, $activeRuntime
]);

$executorResponse = \curl_exec($ch);
$executorResponse = json_decode($executorResponse, true);

$statusCode = \curl_getinfo($ch, CURLINFO_HTTP_CODE);

Expand All @@ -538,13 +540,19 @@ function (string $runtimeId, array $vars, string $data, $timeout, $activeRuntime

switch (true) {
case $statusCode >= 500:
$stderr = $executorResponse ?? 'Internal Runtime error.';
$stderr = ($executorResponse ?? [])['stderr'] ?? 'Internal Runtime error.';
$stdout = ($executorResponse ?? [])['stdout'] ?? 'Internal Runtime error.';
break;
case $statusCode >= 100:
$stdout = $executorResponse;
$stdout = $executorResponse['stdout'];
$res = $executorResponse['response'];
if (is_array($res)) {
$res = json_encode($res, JSON_UNESCAPED_UNICODE);
}
break;
default:
$stderr = $executorResponse ?? 'Execution failed.';
$stderr = ($executorResponse ?? [])['stderr'] ?? 'Execution failed.';
$stdout = ($executorResponse ?? [])['stdout'] ?? '';
break;
}

Expand All @@ -557,7 +565,8 @@ function (string $runtimeId, array $vars, string $data, $timeout, $activeRuntime
$execution = [
'status' => $functionStatus,
'statusCode' => $statusCode,
'response' => \mb_strcut($stdout, 0, 1000000), // Limit to 1MB
'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
'time' => $executionTime,
];
Expand Down Expand Up @@ -648,7 +657,7 @@ function (string $runtimeId, array $vars, string $data, $timeout, $activeRuntime
/**
* Warmup: make sure images are ready to run fast 🚀
*/
$runtimes = new Runtimes('v1');
$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) {
Expand Down
41 changes: 28 additions & 13 deletions app/views/console/functions/function.phtml
Original file line number Diff line number Diff line change
Expand Up @@ -392,10 +392,10 @@ sort($patterns);
<tr>
<th width="30"></th>
<th width="160">Created</th>
<th width="150">Status</th>
<th width="120">Trigger</th>
<th width="80">Runtime</th>
<th></th>
<th width="100">Status</th>
<th width="80">Trigger</th>
<th width="60">Runtime</th>
<th width=""></th>
</tr>
</thead>
<tbody data-ls-loop="project-function-executions.executions" data-ls-as="execution">
Expand All @@ -416,29 +416,44 @@ sort($patterns);
<td data-title="Trigger: ">
<span data-ls-bind="{{execution.trigger}}"></span>
</td>
<td data-title="Runtime: ">
<td data-title="Time: ">
<span data-ls-if="{{execution.status}} === 'completed' || {{execution.status}} === 'failed'" data-ls-bind="{{execution.time|seconds2hum}}"></span>
<span data-ls-if="{{execution.status}} === 'waiting' || {{execution.status}} === 'processing'">-</span>
</td>
<td data-title="">
<div data-ls-if="{{execution.status}} === 'completed' || {{execution.status}} === 'failed'" data-title="">
<div data-ls-if="{{execution.status}} === 'completed' || {{execution.status}} === 'failed'" data-title="" style="display: flex;">
<button class="desktops-only pull-end link margin-start text-danger" data-ls-ui-trigger="execution-stderr-{{execution.$id}}">Stderr</button>
<button class="desktops-only pull-end link margin-start" data-ls-ui-trigger="execution-stdout-{{execution.$id}}">Stdout</button>
<button class="desktops-only pull-end link margin-start" data-ls-ui-trigger="execution-response-{{execution.$id}}">Response</button>

<button class="desktops-only pull-end link margin-start text-danger" data-ls-ui-trigger="execution-stderr-{{execution.$id}}">Errors</button>
<button class="desktops-only pull-end link margin-start" data-ls-ui-trigger="execution-stdout-{{execution.$id}}">Output</button>
<button class="phones-only-inline tablets-only-inline link margin-end-small" data-ls-ui-trigger="execution-response-{{execution.$id}}">Response</button>
<button class="phones-only-inline tablets-only-inline link margin-end-small" data-ls-ui-trigger="execution-stdout-{{execution.$id}}">Stdout</button>
<button class="phones-only-inline tablets-only-inline link text-danger" data-ls-ui-trigger="execution-stderr-{{execution.$id}}">Stderr</button>

<button class="phones-only-inline tablets-only-inline link margin-end-small" data-ls-ui-trigger="execution-stdout-{{execution.$id}}">Output</button>
<button class="phones-only-inline tablets-only-inline link text-danger" data-ls-ui-trigger="execution-stderr-{{execution.$id}}">Errors</button>

<div data-ui-modal class="modal width-large box close" data-button-alias="none" data-open-event="execution-stdout-{{execution.$id}}">
<div data-ui-modal class="modal width-large box close" data-button-alias="none" data-open-event="execution-response-{{execution.$id}}">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>

<h1>STDOUT</h1>
<h1>RESPONSE</h1>

<div class="margin-bottom ide" data-ls-if="({{execution.response.length}})">
<pre data-ls-bind="{{execution.response}}"></pre>
</div>

<div class="margin-bottom" data-ls-if="(!{{execution.response.length}})">
<p>No Response was logged.</p>
</div>
</div>

<div data-ui-modal class="modal width-large box close" data-button-alias="none" data-open-event="execution-stdout-{{execution.$id}}">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>

<h1>STDOUT</h1>

<div class="margin-bottom ide" data-ls-if="({{execution.stdout.length}})">
<pre data-ls-bind="{{execution.stdout}}"></pre>
</div>

<div class="margin-bottom" data-ls-if="(!{{execution.stdout.length}})">
<p>No output was logged.</p>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions app/workers/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ private function execute(
->setAttribute('status', $executionResponse['status'])
->setAttribute('statusCode', $executionResponse['statusCode'])
->setAttribute('response', $executionResponse['response'])
->setAttribute('stdout', $executionResponse['stdout'])
->setAttribute('stderr', $executionResponse['stderr'])
->setAttribute('time', $executionResponse['time']);
} catch (\Throwable $th) {
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"ext-zlib": "*",
"ext-sockets": "*",
"appwrite/php-clamav": "1.1.*",
"appwrite/php-runtimes": "0.10.*",
"appwrite/php-runtimes": "0.11.*",
"utopia-php/framework": "0.20.*",
"utopia-php/logger": "0.3.*",
"utopia-php/abuse": "0.7.*",
Expand Down
10 changes: 5 additions & 5 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion src/Appwrite/Utopia/Response/Model/Execution.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,15 @@ public function __construct()
'default' => '',
'example' => '',
])
->addRule('stdout', [
'type' => self::TYPE_STRING,
'description' => 'The script stdout output string. Logs the last 4,000 characters of the execution stdout output. This will return an empty string unless the response is returned using an API key or as part of a webhook payload.',
'default' => '',
'example' => '',
])
->addRule('stderr', [
'type' => self::TYPE_STRING,
'description' => 'The script stderr output string. Logs the last 4,000 characters of the execution stderr output',
'description' => 'The script stderr output string. Logs the last 4,000 characters of the execution stderr output. This will return an empty string unless the response is returned using an API key or as part of a webhook payload.',
'default' => '',
'example' => '',
])
Expand Down
3 changes: 3 additions & 0 deletions tests/e2e/Services/Functions/FunctionsCustomClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,9 @@ public function testSynchronousExecution(): array
$this->assertEquals($this->getUser()['$id'], $output['APPWRITE_FUNCTION_USER_ID']);
$this->assertNotEmpty($output['APPWRITE_FUNCTION_JWT']);
$this->assertEquals($projectId, $output['APPWRITE_FUNCTION_PROJECT_ID']);
// Client should never see logs and errors
$this->assertEmpty($execution['body']['stdout']);
$this->assertEmpty($execution['body']['stderr']);

// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
Expand Down
5 changes: 3 additions & 2 deletions tests/e2e/Services/Functions/FunctionsCustomServerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,7 @@ public function testCreateCustomPHPExecution()
$this->assertEquals('', $output['APPWRITE_FUNCTION_USER_ID']);
$this->assertEmpty($output['APPWRITE_FUNCTION_JWT']);
$this->assertEquals($this->getProject()['$id'], $output['APPWRITE_FUNCTION_PROJECT_ID']);
$this->assertStringContainsString('Amazing Function Log', $executions['body']['stdout']);

$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
Expand Down Expand Up @@ -893,7 +894,7 @@ public function testCreateCustomPHPExecution()

public function testCreateCustomNodeExecution()
{
$name = 'node-17.0';
$name = 'node-18.0';
$folder = 'node';
$code = realpath(__DIR__ . '/../../../resources/functions') . "/$folder/code.tar.gz";
$this->packageCode($folder);
Expand Down Expand Up @@ -964,7 +965,7 @@ public function testCreateCustomNodeExecution()
$this->assertEquals($deploymentId, $output['APPWRITE_FUNCTION_DEPLOYMENT']);
$this->assertEquals('http', $output['APPWRITE_FUNCTION_TRIGGER']);
$this->assertEquals('Node.js', $output['APPWRITE_FUNCTION_RUNTIME_NAME']);
$this->assertEquals('17.0', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals('18.0', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']);
$this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT']);
$this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT_DATA']);
$this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']);
Expand Down
2 changes: 2 additions & 0 deletions tests/resources/functions/php-fn/index.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php

return function ($request, $response) {
\var_dump("Amazing Function Log"); // We test logs (stdout) visibility with this

$response->json([
'APPWRITE_FUNCTION_ID' => $request['env']['APPWRITE_FUNCTION_ID'],
'APPWRITE_FUNCTION_NAME' => $request['env']['APPWRITE_FUNCTION_NAME'],
Expand Down