From a012a42008b9a6490829fce89ea5de9a621a975e Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Fri, 11 Aug 2023 17:41:48 -0700 Subject: [PATCH 1/5] Create 1.4 request filters --- app/controllers/general.php | 4 ++ src/Appwrite/Utopia/Request/Filters/V16.php | 49 ++++++++++++++++++ tests/unit/Utopia/Request/Filters/V16Test.php | 51 +++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 src/Appwrite/Utopia/Request/Filters/V16.php create mode 100644 tests/unit/Utopia/Request/Filters/V16Test.php diff --git a/app/controllers/general.php b/app/controllers/general.php index 59821296d8..bf3030617c 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -36,6 +36,7 @@ use Appwrite\Utopia\Request\Filters\V12 as RequestV12; use Appwrite\Utopia\Request\Filters\V13 as RequestV13; use Appwrite\Utopia\Request\Filters\V14 as RequestV14; use Appwrite\Utopia\Request\Filters\V15 as RequestV15; +use Appwrite\Utopia\Request\Filters\V16 as RequestV16; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; @@ -222,6 +223,9 @@ App::init() case version_compare($requestFormat, '0.15.3', '<'): Request::setFilter(new RequestV15()); break; + case version_compare($requestFormat, '1.4.0', '<'): + Request::setFilter(new RequestV16()); + break; default: Request::setFilter(null); } diff --git a/src/Appwrite/Utopia/Request/Filters/V16.php b/src/Appwrite/Utopia/Request/Filters/V16.php new file mode 100644 index 0000000000..f8c97681b2 --- /dev/null +++ b/src/Appwrite/Utopia/Request/Filters/V16.php @@ -0,0 +1,49 @@ +filter = new V16(); + } + + public function tearDown(): void + { + } + + public function createExecutionProvider(): array + { + return [ + 'data' => [ + [ + 'data' => 'Lorem ipsum' + ], + [ + 'body' => 'Lorem ipsum' + ], + ], + ]; + } + + /** + * @dataProvider createExecutionProvider + */ + public function testCreateExecution(array $content, array $expected): void + { + $model = 'functions.createExecution'; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } +} From 6184b6a3780fa8c4134aadef500dcc254a8e8ffe Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Mon, 21 Aug 2023 11:43:06 -0700 Subject: [PATCH 2/5] Update request filter to not handle projects apis The endpoints were only used internally so it's okay to not support older versions. --- src/Appwrite/Utopia/Request/Filters/V16.php | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/Appwrite/Utopia/Request/Filters/V16.php b/src/Appwrite/Utopia/Request/Filters/V16.php index f8c97681b2..a0e816b3a8 100644 --- a/src/Appwrite/Utopia/Request/Filters/V16.php +++ b/src/Appwrite/Utopia/Request/Filters/V16.php @@ -23,24 +23,13 @@ class V16 extends Filter unset($content['data']); break; case 'projects.createDomain': - // TODO: How to handle this? - // This endpoint was deleted - break; case 'projects.listDomains': - // TODO: How to handle this? - // This endpoint was deleted - break; case 'projects.getDomain': - // TODO: How to handle this? - // This endpoint was deleted - break; case 'projects.updateDomainVerification': - // TODO: How to handle this? - // This endpoint was deleted - break; case 'projects.deleteDomain': - // TODO: How to handle this? - // This endpoint was deleted + // These endpoints were deleted and we're accepting + // the breaking change since the endpoint was only + // used internally. break; } From eea43e17bde21584f06003b97d257f30a60f15ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 22 Aug 2023 15:16:07 +0200 Subject: [PATCH 3/5] Fix function request filter --- app/config/errors.php | 2 +- app/controllers/api/functions.php | 16 +++++++---- app/controllers/api/proxy.php | 2 +- app/workers/builds.php | 4 +++ src/Appwrite/Utopia/Request/Filters/V16.php | 32 ++++++++++++--------- 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/app/config/errors.php b/app/config/errors.php index 00b177480d..dfa3d0eec5 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -409,7 +409,7 @@ return [ ], Exception::FUNCTION_ENTRYPOINT_MISSING => [ 'name' => Exception::FUNCTION_RUNTIME_UNSUPPORTED, - 'description' => 'Entrypoint missing. Specify it in function settings.', + 'description' => 'Function entrypoint is not configured. Please specify it in function settings or when making deployment.', 'code' => 404, ], diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index fcc9d1fea2..bd5f2d5109 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -134,14 +134,14 @@ App::post('/v1/functions') ->label('sdk.response.model', Response::MODEL_FUNCTION) ->param('functionId', '', new CustomId(), 'Function ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.') ->param('name', '', new Text(128), 'Function name. Max length: 128 chars.') - ->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.') ->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution roles. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true) + ->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.') ->param('events', [], new ArrayList(new ValidatorEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true) ->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true) ->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Function maximum execution time in seconds.', true) ->param('enabled', true, new Boolean(), 'Is function enabled?', true) ->param('logging', true, new Boolean(), 'Do executions get logged?', true) - ->param('entrypoint', '', new Text(1028), 'Entrypoint File.') + ->param('entrypoint', '', new Text(1028, 0), 'Entrypoint File.', true) ->param('commands', '', new Text(8192, 0), 'Build Commands.', true) ->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for vcs deployment.', true) ->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the function', true) @@ -228,7 +228,7 @@ App::post('/v1/functions') 'active' => false, ])) ); - + $function->setAttribute('scheduleId', $schedule->getId()); $function->setAttribute('scheduleInternalId', $schedule->getInternalId()); @@ -607,14 +607,14 @@ App::put('/v1/functions/:functionId') ->label('sdk.response.model', Response::MODEL_FUNCTION) ->param('functionId', '', new UID(), 'Function ID.') ->param('name', '', new Text(128), 'Function name. Max length: 128 chars.') - ->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.') ->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of strings with execution roles. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true) + ->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.') ->param('events', [], new ArrayList(new ValidatorEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true) ->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true) ->param('timeout', 15, new Range(1, (int) App::getEnv('_APP_FUNCTIONS_TIMEOUT', 900)), 'Maximum execution time in seconds.', true) ->param('enabled', true, new Boolean(), 'Is function enabled?', true) ->param('logging', true, new Boolean(), 'Do executions get logged?', true) - ->param('entrypoint', '', new Text(1028), 'Entrypoint File.') + ->param('entrypoint', '', new Text(1028, 0), 'Entrypoint File.', true) ->param('commands', '', new Text(8192, 0), 'Build Commands.', true) ->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for vcs deployment.', true) ->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the function', true) @@ -656,6 +656,10 @@ App::put('/v1/functions/:functionId') $repositoryId = $function->getAttribute('repositoryId', ''); $repositoryInternalId = $function->getAttribute('repositoryInternalId', ''); + if (empty($entrypoint)) { + $entrypoint = $function->getAttribute('entrypoint', ''); + } + $isConnected = !empty($function->getAttribute('providerRepositoryId', '')); // Git disconnect logic @@ -803,7 +807,7 @@ App::get('/v1/functions/:functionId/deployments/:deploymentId/download') ->setContentType('application/gzip') ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache ->addHeader('X-Peak', \memory_get_peak_usage()) - ->addHeader('Content-Disposition', 'attachment; filename="'.$deploymentId.'.tar.gz"') + ->addHeader('Content-Disposition', 'attachment; filename="' . $deploymentId . '.tar.gz"') ; $size = $deviceFunctions->getFileSize($path); diff --git a/app/controllers/api/proxy.php b/app/controllers/api/proxy.php index 1bd01a76e6..25584fab52 100644 --- a/app/controllers/api/proxy.php +++ b/app/controllers/api/proxy.php @@ -45,7 +45,7 @@ App::post('/v1/proxy/rules') if ($domain === $mainDomain || $domain === 'localhost' || $domain === APP_HOSTNAME_INTERNAL) { throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed for security reasons.'); } - + $document = $dbForConsole->findOne('rules', [ Query::equal('domain', [$domain]), ]); diff --git a/app/workers/builds.php b/app/workers/builds.php index f25a2ec7ce..47b9806aae 100644 --- a/app/workers/builds.php +++ b/app/workers/builds.php @@ -86,6 +86,10 @@ class BuildsV1 extends Worker throw new Exception('Deployment not found', 404); } + if (empty($deployment->getAttribute('entrypoint', ''))) { + throw new Exception('Function entrypoint is not configured. Please specify it in function settings or when making deployment.', 500); + } + $runtimes = Config::getParam('runtimes', []); $key = $function->getAttribute('runtime'); $runtime = isset($runtimes[$key]) ? $runtimes[$key] : null; diff --git a/src/Appwrite/Utopia/Request/Filters/V16.php b/src/Appwrite/Utopia/Request/Filters/V16.php index a0e816b3a8..b2d12b0974 100644 --- a/src/Appwrite/Utopia/Request/Filters/V16.php +++ b/src/Appwrite/Utopia/Request/Filters/V16.php @@ -6,33 +6,37 @@ use Appwrite\Utopia\Request\Filter; class V16 extends Filter { - // Convert 1.0 params to 1.4 + // Convert 1.3 params to 1.4 public function parse(array $content, string $model): array { switch ($model) { case 'functions.create': - // TODO: How to handle this? - $content['entrypoint'] = ' '; + $content['commands'] = $this->getCommands($content['runtime']); break; case 'functions.update': - // TODO: How to handle this? - $content['runtime'] = ' '; + $content['commands'] = $this->getCommands($content['runtime']); break; case 'functions.createExecution': $content['body'] = $content['data']; unset($content['data']); break; - case 'projects.createDomain': - case 'projects.listDomains': - case 'projects.getDomain': - case 'projects.updateDomainVerification': - case 'projects.deleteDomain': - // These endpoints were deleted and we're accepting - // the breaking change since the endpoint was only - // used internally. - break; } return $content; } + + private function getCommands(string $runtime): string + { + if (\str_starts_with($runtime, 'node')) { + return 'npm install'; + } elseif (\str_starts_with($runtime, 'python')) { + return 'pip install --no-cache-dir -r requirements.txt'; + } elseif (\str_starts_with($runtime, 'dart')) { + return 'dart pub get'; + } elseif (\str_starts_with($runtime, 'php')) { + return 'composer update --no-interaction --ignore-platform-reqs --optimize-autoloader --prefer-dist --no-dev'; + } + + return ''; + } } From 00dd77e93095c1bd54fbb05be89cb0f040feb4b9 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 22 Aug 2023 14:14:20 -0400 Subject: [PATCH 4/5] Fix exception condition --- app/controllers/api/proxy.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/proxy.php b/app/controllers/api/proxy.php index 8cc765c2ce..442bab740d 100644 --- a/app/controllers/api/proxy.php +++ b/app/controllers/api/proxy.php @@ -42,7 +42,10 @@ App::post('/v1/proxy/rules') ->inject('dbForProject') ->action(function (string $domain, string $resourceType, string $resourceId, Response $response, Document $project, Event $events, Database $dbForConsole, Database $dbForProject) { $mainDomain = App::getEnv('_APP_DOMAIN', ''); - if ($domain === $mainDomain || $domain === 'localhost' || $domain === APP_HOSTNAME_INTERNAL) { + if ($domain === $mainDomain) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your main domain to specific resource. Please use subdomain or a different domain.'); + } + if ($domain === 'localhost' || $domain === APP_HOSTNAME_INTERNAL) { throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.'); } From aec59074df51e0426b9967dd71729c773fd8d406 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 22 Aug 2023 17:24:27 -0400 Subject: [PATCH 5/5] Update src/Appwrite/Utopia/Request/Filters/V16.php --- src/Appwrite/Utopia/Request/Filters/V16.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Appwrite/Utopia/Request/Filters/V16.php b/src/Appwrite/Utopia/Request/Filters/V16.php index b2d12b0974..132955a980 100644 --- a/src/Appwrite/Utopia/Request/Filters/V16.php +++ b/src/Appwrite/Utopia/Request/Filters/V16.php @@ -35,6 +35,12 @@ class V16 extends Filter return 'dart pub get'; } elseif (\str_starts_with($runtime, 'php')) { return 'composer update --no-interaction --ignore-platform-reqs --optimize-autoloader --prefer-dist --no-dev'; + } elseif (\str_starts_with($runtime, 'ruby')) { + return 'bundle install'; + } elseif (\str_starts_with($runtime, 'swift')) { + return 'swift package resolve'; + } elseif (\str_starts_with($runtime, 'dotnet')) { + return 'dotnet restore'; } return '';