diff --git a/app/config/errors.php b/app/config/errors.php index eb8c040c0e..a6d97ddacc 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -414,7 +414,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 73ca387423..0c870ae0f2 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) @@ -660,14 +660,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) @@ -709,6 +709,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 diff --git a/app/controllers/general.php b/app/controllers/general.php index 78829eef20..b031ba3442 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -37,6 +37,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; @@ -223,6 +224,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/app/workers/builds.php b/app/workers/builds.php index 9e3d75bb05..42b5030cfd 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 new file mode 100644 index 0000000000..132955a980 --- /dev/null +++ b/src/Appwrite/Utopia/Request/Filters/V16.php @@ -0,0 +1,48 @@ +getCommands($content['runtime']); + break; + case 'functions.update': + $content['commands'] = $this->getCommands($content['runtime']); + break; + case 'functions.createExecution': + $content['body'] = $content['data']; + unset($content['data']); + 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'; + } 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 ''; + } +} diff --git a/tests/unit/Utopia/Request/Filters/V16Test.php b/tests/unit/Utopia/Request/Filters/V16Test.php new file mode 100644 index 0000000000..25b97a0ce4 --- /dev/null +++ b/tests/unit/Utopia/Request/Filters/V16Test.php @@ -0,0 +1,51 @@ +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); + } +}