diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3e5cb35721..633bd46ea4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -212,7 +212,17 @@ jobs: name: benchmark.json path: benchmark.json retention-days: 7 - - name: Comment on PR - uses: thollander/actions-comment-pull-request@v2 + - name: Find Comment + uses: peter-evans/find-comment@v3 + id: fc with: - filePath: benchmark.txt + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: Benchmark results + - name: Comment on PR + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body-path: benchmark.txt + edit-mode: replace diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 443260ea90..69f7f40872 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -431,13 +431,13 @@ App::get('/v1/functions/runtimes') $allowList = \array_filter(\explode(',', System::getEnv('_APP_FUNCTIONS_RUNTIMES', ''))); $allowed = []; - foreach ($runtimes as $key => $runtime) { - if (!empty($allowList) && !\in_array($key, $allowList)) { + foreach ($runtimes as $id => $runtime) { + if (!empty($allowList) && !\in_array($id, $allowList)) { continue; } - $runtimes[$key]['$id'] = $key; - $allowed[] = $runtimes[$key]; + $runtime['$id'] = $id; + $allowed[] = $runtime; } $response->dynamic(new Document([ @@ -1878,7 +1878,7 @@ App::post('/v1/functions/:functionId/executions') } /** Update execution status */ - $status = $executionResponse['statusCode'] >= 400 ? 'failed' : 'completed'; + $status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed'; $execution->setAttribute('status', $status); $execution->setAttribute('responseStatusCode', $executionResponse['statusCode']); $execution->setAttribute('responseHeaders', $headersFiltered); diff --git a/app/controllers/general.php b/app/controllers/general.php index d532110df3..95e6dc18df 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -302,7 +302,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo } /** Update execution status */ - $status = $executionResponse['statusCode'] >= 400 ? 'failed' : 'completed'; + $status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed'; $execution->setAttribute('status', $status); $execution->setAttribute('responseStatusCode', $executionResponse['statusCode']); $execution->setAttribute('responseHeaders', $headersFiltered); diff --git a/src/Appwrite/Platform/Workers/Builds.php b/src/Appwrite/Platform/Workers/Builds.php index d0fb597258..818dbf89d1 100644 --- a/src/Appwrite/Platform/Workers/Builds.php +++ b/src/Appwrite/Platform/Workers/Builds.php @@ -2,6 +2,7 @@ namespace Appwrite\Platform\Workers; +use Ahc\Jwt\JWT; use Appwrite\Event\Event; use Appwrite\Event\Func; use Appwrite\Event\Usage; @@ -393,8 +394,21 @@ class Builds extends Action $vars[$var->getAttribute('key')] = $var->getAttribute('value', ''); } + $jwtExpiry = (int) System::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900); + $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0); + $apiKey = $jwtObj->encode([ + 'projectId' => $project->getId(), + 'scopes' => $function->getAttribute('scopes', []) + ]); + + $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https'; + $hostname = System::getEnv('_APP_DOMAIN'); + $endpoint = $protocol . '://' . $hostname . "/v1"; + // Appwrite vars $vars = \array_merge($vars, [ + 'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint, + 'APPWRITE_FUNCTION_API_KEY' => API_KEY_DYNAMIC . '_' . $apiKey, 'APPWRITE_FUNCTION_ID' => $function->getId(), 'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'), 'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(), diff --git a/src/Appwrite/Platform/Workers/Functions.php b/src/Appwrite/Platform/Workers/Functions.php index 78671bfeb0..8ade08dc2d 100644 --- a/src/Appwrite/Platform/Workers/Functions.php +++ b/src/Appwrite/Platform/Workers/Functions.php @@ -494,7 +494,7 @@ class Functions extends Action logging: $function->getAttribute('logging', true), ); - $status = $executionResponse['statusCode'] >= 400 ? 'failed' : 'completed'; + $status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed'; $headersFiltered = []; foreach ($executionResponse['headers'] as $key => $value) { diff --git a/src/Appwrite/Utopia/Response/Filters/V18.php b/src/Appwrite/Utopia/Response/Filters/V18.php index d0aa680e3b..6485b6f0ba 100644 --- a/src/Appwrite/Utopia/Response/Filters/V18.php +++ b/src/Appwrite/Utopia/Response/Filters/V18.php @@ -24,6 +24,12 @@ class V18 extends Filter protected function parseExecution(array $content) { + if(!empty($content['status']) && !empty($content['statusCode'])) { + if($content['status'] === 'completed' && $content['statusCode'] >= 400 && $content['statusCode'] < 500) { + $content['status'] = 'failed'; + } + } + unset($content['scheduledAt']); return $content; } diff --git a/src/Appwrite/Utopia/Response/Model/Runtime.php b/src/Appwrite/Utopia/Response/Model/Runtime.php index 8bc42cb418..3c328c4d40 100644 --- a/src/Appwrite/Utopia/Response/Model/Runtime.php +++ b/src/Appwrite/Utopia/Response/Model/Runtime.php @@ -16,6 +16,12 @@ class Runtime extends Model 'default' => '', 'example' => 'python-3.8', ]) + ->addRule('key', [ + 'type' => self::TYPE_STRING, + 'description' => 'Parent runtime key.', + 'default' => '', + 'example' => 'python', + ]) ->addRule('name', [ 'type' => self::TYPE_STRING, 'description' => 'Runtime Name.', diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index e3148752c8..5408d0f718 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -802,6 +802,23 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals('', $execution['body']['logs']); $this->assertLessThan(10, $execution['body']['duration']); + $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $data['functionId'] . '/executions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'async' => false, + 'path' => '/?code=400' + ]); + $this->assertEquals(201, $execution['headers']['status-code']); + $this->assertEquals('completed', $execution['body']['status']); + $this->assertEquals(400, $execution['body']['responseStatusCode']); + + $execution = $this->client->call(Client::METHOD_DELETE, '/functions/' . $data['functionId'] . '/executions/' . $execution['body']['$id'], array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + $this->assertEquals(204, $execution['headers']['status-code']); + return array_merge($data, ['executionId' => $executionId]); } @@ -1412,6 +1429,7 @@ class FunctionsCustomServerTest extends Scope $this->assertArrayHasKey('$id', $runtime); $this->assertArrayHasKey('name', $runtime); + $this->assertArrayHasKey('key', $runtime); $this->assertArrayHasKey('version', $runtime); $this->assertArrayHasKey('logo', $runtime); $this->assertArrayHasKey('image', $runtime); @@ -1544,6 +1562,7 @@ class FunctionsCustomServerTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'entrypoint' => 'index.php', + 'commands' => 'sh setup.sh && composer install', 'code' => new CURLFile($code, 'application/x-gzip', basename($code)), 'activate' => true ]); @@ -1553,6 +1572,16 @@ class FunctionsCustomServerTest extends Scope $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); + $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertStringContainsStringIgnoringCase("200 OK", $deployment['body']['buildLogs']); + $this->assertStringContainsStringIgnoringCase('"total":', $deployment['body']['buildLogs']); + $this->assertStringContainsStringIgnoringCase('"users":', $deployment['body']['buildLogs']); + $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], diff --git a/tests/resources/functions/php-scopes/setup.sh b/tests/resources/functions/php-scopes/setup.sh new file mode 100644 index 0000000000..a2f78a4f3d --- /dev/null +++ b/tests/resources/functions/php-scopes/setup.sh @@ -0,0 +1,6 @@ + +ENDPOINT="$APPWRITE_FUNCTION_API_ENDPOINT/users" +PROJECT_ID="$APPWRITE_FUNCTION_PROJECT_ID" +API_KEY="$APPWRITE_FUNCTION_API_KEY" + +curl -v -X GET $ENDPOINT -H "x-appwrite-project: $PROJECT_ID" -H "x-appwrite-key: $API_KEY" \ No newline at end of file diff --git a/tests/resources/functions/php/index.php b/tests/resources/functions/php/index.php index d5328c40e1..e8e5eb67d5 100644 --- a/tests/resources/functions/php/index.php +++ b/tests/resources/functions/php/index.php @@ -1,6 +1,8 @@ req->query['code'] ?? '200'; + return $context->res->json([ 'APPWRITE_FUNCTION_ID' => \getenv('APPWRITE_FUNCTION_ID') ?: '', 'APPWRITE_FUNCTION_NAME' => \getenv('APPWRITE_FUNCTION_NAME') ?: '', @@ -11,5 +13,5 @@ return function ($context) { 'APPWRITE_REGION' => \getenv('APPWRITE_REGION') ?: '', 'UNICODE_TEST' => "êä", 'GLOBAL_VARIABLE' => \getenv('GLOBAL_VARIABLE') ?: '' - ]); + ], \intval($statusCode)); }; diff --git a/tests/unit/Utopia/Response/Filters/V18Test.php b/tests/unit/Utopia/Response/Filters/V18Test.php index c4011c08a1..5396779b77 100644 --- a/tests/unit/Utopia/Response/Filters/V18Test.php +++ b/tests/unit/Utopia/Response/Filters/V18Test.php @@ -34,7 +34,7 @@ class V18Test extends TestCase ], [ ] - ] + ], ]; } @@ -60,6 +60,46 @@ class V18Test extends TestCase ], [ ] + ], + 'update 404 status' => [ + [ + 'statusCode' => '404', + 'status' => 'completed' + ], + [ + 'statusCode' => '404', + 'status' => 'failed' + ] + ], + 'update 400 status' => [ + [ + 'statusCode' => '400', + 'status' => 'completed' + ], + [ + 'statusCode' => '400', + 'status' => 'failed' + ] + ], + 'dont update 200 status' => [ + [ + 'statusCode' => '200', + 'status' => 'completed' + ], + [ + 'statusCode' => '200', + 'status' => 'completed' + ] + ], + 'dont update 500 status' => [ + [ + 'statusCode' => '500', + 'status' => 'failed' + ], + [ + 'statusCode' => '500', + 'status' => 'failed' + ] ] ]; }