Merge pull request #9804 from appwrite/fix-function-create-compatibility

Fix: backwards compatibility for function creation
This commit is contained in:
Matej Bačo 2025-05-18 23:15:59 +02:00 committed by GitHub
commit 908d80e8d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 211 additions and 20 deletions

View file

@ -67,7 +67,7 @@
"utopia-php/platform": "0.7.*",
"utopia-php/pools": "0.8.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/queue": "0.9.*",
"utopia-php/queue": "0.10.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.18.*",
"utopia-php/swoole": "0.8.*",

37
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "735023e2b70ce47fe65985f195b257bd",
"content-hash": "b6f5295db11727cb4e88e702dc97809d",
"packages": [
{
"name": "adhocore/jwt",
@ -4104,16 +4104,16 @@
},
{
"name": "utopia-php/platform",
"version": "0.7.4",
"version": "0.7.6",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/platform.git",
"reference": "a5b93d8177702ec458c3af9137663133c012b71b"
"reference": "6bc7fbb43ec2b7f9ee5bdef5d4b5e4a81860950b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/platform/zipball/a5b93d8177702ec458c3af9137663133c012b71b",
"reference": "a5b93d8177702ec458c3af9137663133c012b71b",
"url": "https://api.github.com/repos/utopia-php/platform/zipball/6bc7fbb43ec2b7f9ee5bdef5d4b5e4a81860950b",
"reference": "6bc7fbb43ec2b7f9ee5bdef5d4b5e4a81860950b",
"shasum": ""
},
"require": {
@ -4122,11 +4122,11 @@
"php": ">=8.0",
"utopia-php/cli": "0.15.*",
"utopia-php/framework": "0.33.*",
"utopia-php/queue": "0.9.*"
"utopia-php/queue": "0.10.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
"phpunit/phpunit": "^9.3"
"laravel/pint": "1.*",
"phpunit/phpunit": "9.*"
},
"type": "library",
"autoload": {
@ -4148,9 +4148,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/platform/issues",
"source": "https://github.com/utopia-php/platform/tree/0.7.4"
"source": "https://github.com/utopia-php/platform/tree/0.7.6"
},
"time": "2025-03-13T13:00:12+00:00"
"time": "2025-05-18T20:31:24+00:00"
},
{
"name": "utopia-php/pools",
@ -4259,16 +4259,16 @@
},
{
"name": "utopia-php/queue",
"version": "0.9.1",
"version": "0.10.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/queue.git",
"reference": "32b6f84c55aae761db5a5ae76cc91ca8dbc8bc32"
"reference": "0eccc559168ea72241c39a4c482d868314666be1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/queue/zipball/32b6f84c55aae761db5a5ae76cc91ca8dbc8bc32",
"reference": "32b6f84c55aae761db5a5ae76cc91ca8dbc8bc32",
"url": "https://api.github.com/repos/utopia-php/queue/zipball/0eccc559168ea72241c39a4c482d868314666be1",
"reference": "0eccc559168ea72241c39a4c482d868314666be1",
"shasum": ""
},
"require": {
@ -4277,6 +4277,7 @@
"utopia-php/cli": "0.15.*",
"utopia-php/fetch": "0.4.*",
"utopia-php/framework": "0.33.*",
"utopia-php/pools": "0.8.*",
"utopia-php/telemetry": "0.1.*"
},
"require-dev": {
@ -4318,9 +4319,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/queue/issues",
"source": "https://github.com/utopia-php/queue/tree/0.9.1"
"source": "https://github.com/utopia-php/queue/tree/0.10.0"
},
"time": "2025-03-28T19:49:36+00:00"
"time": "2025-04-17T12:15:52+00:00"
},
{
"name": "utopia-php/registry",
@ -8240,7 +8241,7 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
@ -8264,5 +8265,5 @@
"platform-overrides": {
"php": "8.3"
},
"plugin-api-version": "2.6.0"
"plugin-api-version": "2.3.0"
}

View file

@ -2,8 +2,12 @@
namespace Appwrite\Platform\Modules\Functions\Http\Functions;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Realtime;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Validator\Specification;
@ -13,6 +17,7 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Task\Validator\Cron;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model\Rule;
use Utopia\Abuse\Abuse;
use Utopia\Config\Config;
use Utopia\Database\Database;
@ -25,12 +30,14 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Roles;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Request;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class Create extends Base
{
@ -91,12 +98,22 @@ class Create extends Base
System::getEnv('_APP_COMPUTE_CPUS', 0),
System::getEnv('_APP_COMPUTE_MEMORY', 0)
), 'Runtime specification for the function and builds.', true, ['plan'])
->param('templateRepository', '', new Text(128, 0), 'Repository name of the template.', true, deprecated: true)
->param('templateOwner', '', new Text(128, 0), 'The name of the owner of the template.', true, deprecated: true)
->param('templateRootDirectory', '', new Text(128, 0), 'Path to function code in the template repo.', true, deprecated: true)
->param('templateVersion', '', new Text(128, 0), 'Version (tag) for the repo linked to the function template.', true, deprecated: true)
->inject('response')
->inject('dbForProject')
->inject('timelimit')
->inject('project')
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('queueForRealtime')
->inject('queueForWebhooks')
->inject('queueForFunctions')
->inject('dbForPlatform')
->inject('request')
->inject('gitHub')
->callback([$this, 'action']);
}
@ -119,12 +136,22 @@ class Create extends Base
bool $providerSilentMode,
string $providerRootDirectory,
string $specification,
string $templateRepository,
string $templateOwner,
string $templateRootDirectory,
string $templateVersion,
Response $response,
Database $dbForProject,
callable $timelimit,
Document $project,
Event $queueForEvents,
Database $dbForPlatform
Build $queueForBuilds,
Realtime $queueForRealtime,
Webhook $queueForWebhooks,
Func $queueForFunctions,
Database $dbForPlatform,
Request $request,
GitHub $github
) {
// Temporary abuse check
@ -251,6 +278,136 @@ class Create extends Base
$function = $dbForProject->updateDocument('functions', $function->getId(), $function);
// Backwards compatibility with 1.6 behaviour
$requestFormat = $request->getHeader('x-appwrite-response-format', System::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
if ($requestFormat && version_compare($requestFormat, '1.7.0', '<')) {
// build from template
$template = new Document([]);
if (
!empty($templateRepository)
&& !empty($templateOwner)
&& !empty($templateRootDirectory)
&& !empty($templateVersion)
) {
$template->setAttribute('repositoryName', $templateRepository)
->setAttribute('ownerName', $templateOwner)
->setAttribute('rootDirectory', $templateRootDirectory)
->setAttribute('version', $templateVersion);
}
if (!empty($providerRepositoryId)) {
// Deploy VCS
$template = new Document();
$installation = $dbForPlatform->getDocument('installations', $function->getAttribute('installationId'));
$deployment = $this->redeployVcsFunction(
request: $request,
function: $function,
project: $project,
installation: $installation,
dbForProject: $dbForProject,
queueForBuilds: $queueForBuilds,
template: $template,
github: $github,
activate: true,
reference: $providerBranch,
referenceType: 'branch'
);
$function = $function
->setAttribute('latestDeploymentId', $deployment->getId())
->setAttribute('latestDeploymentInternalId', $deployment->getInternalId())
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument('functions', $function->getId(), $function);
} elseif (!$template->isEmpty()) {
// Deploy non-VCS from template
$deploymentId = ID::unique();
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getInternalId(),
'resourceType' => 'functions',
'entrypoint' => $function->getAttribute('entrypoint', ''),
'buildCommands' => $function->getAttribute('commands', ''),
'type' => 'manual',
'activate' => true,
]));
$function = $function
->setAttribute('latestDeploymentId', $deployment->getId())
->setAttribute('latestDeploymentInternalId', $deployment->getInternalId())
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
$dbForProject->updateDocument('functions', $function->getId(), $function);
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setTemplate($template);
}
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
if (!empty($functionsDomain)) {
$routeSubdomain = ID::unique();
$domain = "{$routeSubdomain}.{$functionsDomain}";
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique();
$rule = Authorization::skip(
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'status' => 'verified',
'type' => 'deployment',
'trigger' => 'manual',
'deploymentId' => !isset($deployment) || $deployment->isEmpty() ? '' : $deployment->getId(),
'deploymentInternalId' => !isset($deployment) || $deployment->isEmpty() ? '' : $deployment->getInternalId(),
'deploymentResourceType' => 'function',
'deploymentResourceId' => $function->getId(),
'deploymentResourceInternalId' => $function->getInternalId(),
'deploymentVcsProviderBranch' => '',
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),
'owner' => 'Appwrite',
'region' => $project->getAttribute('region')
]))
);
$ruleModel = new Rule();
$ruleCreate =
$queueForEvents
->setProject($project)
->setEvent('rules.[ruleId].create')
->setParam('ruleId', $rule->getId())
->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())));
/** Trigger Webhook */
$queueForWebhooks
->from($ruleCreate)
->trigger();
/** Trigger Functions */
$queueForFunctions
->from($ruleCreate)
->trigger();
/** Trigger Realtime Events */
$queueForRealtime
->from($ruleCreate)
->setSubscribers(['console', $project->getId()])
->trigger();
}
}
$queueForEvents->setParam('functionId', $function->getId());
$response

View file

@ -309,6 +309,10 @@ class OpenAPI3 extends Format
$bodyRequired = [];
foreach ($route->getParams() as $name => $param) { // Set params
if ($param['deprecated']) {
continue;
}
/**
* @var \Utopia\Validator $validator
*/

View file

@ -314,6 +314,10 @@ class Swagger2 extends Format
);
foreach ($parameters as $name => $param) { // Set params
if ($param['deprecated']) {
continue;
}
/** @var Validator $validator */
$validator = (\is_callable($param['validator']))
? ($param['validator'])(...$this->app->getResources($param['injections']))

View file

@ -10,6 +10,16 @@ class V19 extends Filter
public function parse(array $content, string $model): array
{
switch ($model) {
case 'functions.list':
$content = $this->convertQueryAttribute($content, 'deployment', 'deploymentId');
break;
case 'functions.listDeployments':
$content = $this->convertQueryAttribute($content, 'size', 'deploymentSize');
break;
case 'proxy.listRules':
$content = $this->convertQueryAttribute($content, 'resourceType', 'deploymentResourceType');
$content = $this->convertQueryAttribute($content, 'resourceId', 'deploymentResourceId');
break;
case 'functions.create':
unset($content['templateRepository']);
unset($content['templateOwner']);
@ -28,4 +38,19 @@ class V19 extends Filter
}
return $content;
}
public function convertQueryAttribute(array $content, string $old, string $new)
{
if (isset($content['queries']) && is_array($content['queries'])) {
foreach ($content['queries'] as $index => $query) {
$query = \json_decode($query, true);
if (($query['attribute'] ?? '') === $old) {
$query['attribute'] = $new;
}
$content['queries'][$index] = \json_encode($query);
}
}
return $content;
}
}