Merge pull request #8527 from appwrite/fix-manual-templates

Fix: Manual templates
This commit is contained in:
Christy Jacob 2024-08-15 12:55:56 +04:00 committed by GitHub
commit 78af3ab867
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 338 additions and 98 deletions

View file

@ -553,6 +553,12 @@ To run end-2-end tests for a specific service use:
docker compose exec appwrite test /usr/src/code/tests/e2e/Services/[ServiceName]
```
To run one specific test:
```bash
docker compose exec appwrite vendor/bin/phpunit --filter [FunctionName]
```
## Benchmarking
You can use WRK Docker image to benchmark the server performance. Benchmarking is extremely useful when you want to compare how the server behaves before and after a change has been applied. Replace [APPWRITE_HOSTNAME_OR_IP] with your Appwrite server hostname or IP. Note that localhost is not accessible from inside the WRK container.

View file

@ -80,7 +80,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [],
'scopes' => ["users.read"]
],
@ -106,7 +106,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'UPSTASH_URL',
@ -149,7 +149,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'REDIS_HOST',
@ -191,7 +191,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'NEO4J_URI',
@ -242,7 +242,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'MONGO_URI',
@ -278,7 +278,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'PGHOST',
@ -362,7 +362,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'OPENAI_API_KEY',
@ -416,7 +416,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'DISCORD_PUBLIC_KEY',
@ -466,7 +466,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'PERSPECTIVE_API_KEY',
@ -513,7 +513,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'PANGEA_REDACT_TOKEN',
@ -542,7 +542,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => []
],
[
@ -568,7 +568,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'GITHUB_TOKEN',
@ -610,7 +610,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'APPWRITE_DATABASE_ID',
@ -673,7 +673,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'APPWRITE_DATABASE_ID',
@ -766,7 +766,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'APPWRITE_DATABASE_ID',
@ -865,7 +865,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'VONAGE_API_KEY',
@ -920,7 +920,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'FCM_PROJECT_ID',
@ -987,7 +987,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'SMTP_HOST',
@ -1057,7 +1057,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'STRIPE_SECRET_KEY',
@ -1098,7 +1098,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'STRIPE_SECRET_KEY',
@ -1155,7 +1155,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'HUGGINGFACE_ACCESS_TOKEN',
@ -1188,7 +1188,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'HUGGINGFACE_ACCESS_TOKEN',
@ -1221,7 +1221,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'APPWRITE_DATABASE_ID',
@ -1279,7 +1279,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'APPWRITE_DATABASE_ID',
@ -1337,7 +1337,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'APPWRITE_DATABASE_ID',
@ -1395,7 +1395,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'APPWRITE_DATABASE_ID',
@ -1453,7 +1453,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'REPLICATE_API_KEY',
@ -1487,7 +1487,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'TOGETHER_API_KEY',
@ -1529,7 +1529,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'PERPLEXITY_API_KEY',
@ -1569,7 +1569,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'REPLICATE_API_KEY',
@ -1603,7 +1603,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'OPENAI_API_KEY',
@ -1666,7 +1666,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'OPENAI_API_KEY',
@ -1729,7 +1729,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'ELEVENLABS_API_KEY',
@ -1784,7 +1784,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'LMNT_API_KEY',
@ -1825,7 +1825,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'ANYSCALE_API_KEY',
@ -1865,7 +1865,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'APPWRITE_BUCKET_ID',
@ -1907,7 +1907,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'FAL_API_KEY',
@ -1941,7 +1941,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'LEMON_SQUEEZY_API_KEY',
@ -1996,7 +1996,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates',
'providerOwner' => 'appwrite',
'providerBranch' => 'main',
'providerVersion' => '0.2.*',
'variables' => [
[
'name' => 'APPWRITE_DATABASE_ID',

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -165,7 +165,7 @@ App::post('/v1/functions')
->param('templateRepository', '', new Text(128, 0), 'Repository name of the template.', true)
->param('templateOwner', '', new Text(128, 0), 'The name of the owner of the template.', true)
->param('templateRootDirectory', '', new Text(128, 0), 'Path to function code in the template repo.', true)
->param('templateBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function template.', true)
->param('templateVersion', '', new Text(128, 0), 'Version (tag) for the repo linked to the function template.', true)
->inject('request')
->inject('response')
->inject('dbForProject')
@ -175,7 +175,7 @@ App::post('/v1/functions')
->inject('queueForBuilds')
->inject('dbForConsole')
->inject('gitHub')
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateBranch, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
->action(function (string $functionId, string $name, string $runtime, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $commands, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) use ($redeployVcs) {
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
$allowList = \array_filter(\explode(',', System::getEnv('_APP_FUNCTIONS_RUNTIMES', '')));
@ -190,12 +190,12 @@ App::post('/v1/functions')
!empty($templateRepository)
&& !empty($templateOwner)
&& !empty($templateRootDirectory)
&& !empty($templateBranch)
&& !empty($templateVersion)
) {
$template->setAttribute('repositoryName', $templateRepository)
->setAttribute('ownerName', $templateOwner)
->setAttribute('rootDirectory', $templateRootDirectory)
->setAttribute('branch', $templateBranch);
->setAttribute('version', $templateVersion);
}
$installation = $dbForConsole->getDocument('installations', $installationId);
@ -284,9 +284,34 @@ App::post('/v1/functions')
$function = $dbForProject->updateDocument('functions', $function->getId(), $function);
// Redeploy vcs logic
if (!empty($providerRepositoryId)) {
// Deploy VCS
$redeployVcs($request, $function, $project, $installation, $dbForProject, $queueForBuilds, $template, $github);
} 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', ''),
'commands' => $function->getAttribute('commands', ''),
'type' => 'manual',
'search' => implode(' ', [$deploymentId, $function->getAttribute('entrypoint', '')]),
'activate' => true,
]));
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setTemplate($template);
}
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');

12
composer.lock generated
View file

@ -2759,16 +2759,16 @@
},
{
"name": "utopia-php/vcs",
"version": "0.8.1",
"version": "0.8.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/vcs.git",
"reference": "3084aa93d24ed1e70f01e75f4318fc0d07f12596"
"reference": "eb9b7eade1a46a4f660e0d5a6304f7fa26ec9d18"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/3084aa93d24ed1e70f01e75f4318fc0d07f12596",
"reference": "3084aa93d24ed1e70f01e75f4318fc0d07f12596",
"url": "https://api.github.com/repos/utopia-php/vcs/zipball/eb9b7eade1a46a4f660e0d5a6304f7fa26ec9d18",
"reference": "eb9b7eade1a46a4f660e0d5a6304f7fa26ec9d18",
"shasum": ""
},
"require": {
@ -2802,9 +2802,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/vcs/issues",
"source": "https://github.com/utopia-php/vcs/tree/0.8.1"
"source": "https://github.com/utopia-php/vcs/tree/0.8.2"
},
"time": "2024-07-29T20:49:09+00:00"
"time": "2024-08-13T14:36:30+00:00"
},
{
"name": "utopia-php/websocket",

View file

@ -195,7 +195,7 @@ services:
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: appwrite/console:5.0.0-rc14
image: appwrite/console:5.0.0-rc15
restart: unless-stopped
networks:
- appwrite

View file

@ -6,7 +6,7 @@
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="true"
stopOnFailure="false"
>
<extensions>
<extension class="Appwrite\Tests\TestHook" />

View file

@ -207,7 +207,65 @@ class Builds extends Action
}
try {
if ($isNewBuild && $isVcsEnabled) {
if($isNewBuild && !$isVcsEnabled) {
// Non-vcs+Template
$templateRepositoryName = $template->getAttribute('repositoryName', '');
$templateOwnerName = $template->getAttribute('ownerName', '');
$templateVersion = $template->getAttribute('version', '');
$templateRootDirectory = $template->getAttribute('rootDirectory', '');
$templateRootDirectory = \rtrim($templateRootDirectory, '/');
$templateRootDirectory = \ltrim($templateRootDirectory, '.');
$templateRootDirectory = \ltrim($templateRootDirectory, '/');
if (!empty($templateRepositoryName) && !empty($templateOwnerName) && !empty($templateVersion)) {
$stdout = '';
$stderr = '';
// Clone template repo
$tmpTemplateDirectory = '/tmp/builds/' . $buildId . '-template';
$gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateVersion, GitHub::CLONE_TYPE_TAG, $tmpTemplateDirectory, $templateRootDirectory);
$exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr);
if ($exit !== 0) {
throw new \Exception('Unable to clone code repository: ' . $stderr);
}
// Ensure directories
Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $stdout, $stderr);
$tmpPathFile = $tmpTemplateDirectory . '/code.tar.gz';
$localDevice = new Local();
if (substr($tmpTemplateDirectory, -1) !== '/') {
$tmpTemplateDirectory .= '/';
}
$directorySize = $localDevice->getDirectorySize($tmpTemplateDirectory);
$functionsSizeLimit = (int)System::getEnv('_APP_FUNCTIONS_SIZE_LIMIT', '30000000');
if ($directorySize > $functionsSizeLimit) {
throw new \Exception('Repository directory size should be less than ' . number_format($functionsSizeLimit / 1048576, 2) . ' MBs.');
}
$tarParamDirectory = \escapeshellarg('/tmp/builds/' . $buildId . '-template' . (empty($templateRootDirectory) ? '' : '/' . $templateRootDirectory));
Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $stdout, $stderr); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax
$source = $deviceForFunctions->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
$result = $localDevice->transfer($tmpPathFile, $source, $deviceForFunctions);
if (!$result) {
throw new \Exception("Unable to move file");
}
Console::execute('rm -rf ' . \escapeshellarg($tmpTemplateDirectory), '', $stdout, $stderr);
$build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source));
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment->setAttribute('path', $source));
}
} elseif ($isNewBuild && $isVcsEnabled) {
// VCS and VCS+Temaplte
$tmpDirectory = '/tmp/builds/' . $buildId . '/code';
$rootDirectory = $function->getAttribute('providerRootDirectory', '');
$rootDirectory = \rtrim($rootDirectory, '/');
@ -233,7 +291,8 @@ class Builds extends Action
$gitCloneCommand = $github->generateCloneCommand($cloneOwner, $cloneRepository, $cloneVersion, $cloneType, $tmpDirectory, $rootDirectory);
$stdout = '';
$stderr = '';
Console::execute('mkdir -p /tmp/builds/' . \escapeshellcmd($buildId), '', $stdout, $stderr);
Console::execute('mkdir -p ' . \escapeshellarg('/tmp/builds/' . $buildId), '', $stdout, $stderr);
if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') {
Console::info('Build has been canceled');
@ -263,23 +322,18 @@ class Builds extends Action
// Build from template
$templateRepositoryName = $template->getAttribute('repositoryName', '');
$templateOwnerName = $template->getAttribute('ownerName', '');
$templateBranch = $template->getAttribute('branch', '');
$templateVersion = $template->getAttribute('version', '');
$templateRootDirectory = $template->getAttribute('rootDirectory', '');
$templateRootDirectory = \rtrim($templateRootDirectory, '/');
$templateRootDirectory = \ltrim($templateRootDirectory, '.');
$templateRootDirectory = \ltrim($templateRootDirectory, '/');
if (!empty($templateRepositoryName) && !empty($templateOwnerName) && !empty($templateBranch)) {
if (!empty($templateRepositoryName) && !empty($templateOwnerName) && !empty($templateVersion)) {
// Clone template repo
$tmpTemplateDirectory = '/tmp/builds/' . \escapeshellcmd($buildId) . '/template';
$tmpTemplateDirectory = '/tmp/builds/' . $buildId . '/template';
$cloneType = GitHub::CLONE_TYPE_BRANCH;
if(\str_starts_with($templateBranch, '0.1.')) { // Temporary fix for 1.5. In future versions this only support tag names
$cloneType = GitHub::CLONE_TYPE_TAG;
}
$gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateBranch, $cloneType, $tmpTemplateDirectory, $templateRootDirectory);
$gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateVersion, GitHub::CLONE_TYPE_TAG, $tmpTemplateDirectory, $templateRootDirectory);
$exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr);
if ($exit !== 0) {
@ -287,20 +341,20 @@ class Builds extends Action
}
// Ensure directories
Console::execute('mkdir -p ' . $tmpTemplateDirectory . '/' . $templateRootDirectory, '', $stdout, $stderr);
Console::execute('mkdir -p ' . $tmpDirectory . '/' . $rootDirectory, '', $stdout, $stderr);
Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $stdout, $stderr);
Console::execute('mkdir -p ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $stdout, $stderr);
// Merge template into user repo
Console::execute('rsync -av --exclude \'.git\' ' . $tmpTemplateDirectory . '/' . $templateRootDirectory . '/ ' . $tmpDirectory . '/' . $rootDirectory, '', $stdout, $stderr);
Console::execute('rsync -av --exclude \'.git\' ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory . '/') . ' ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $stdout, $stderr);
// Commit and push
$exit = Console::execute('git config --global user.email "team@appwrite.io" && git config --global user.name "Appwrite" && cd ' . $tmpDirectory . ' && git add . && git commit -m "Create \'' . \escapeshellcmd($function->getAttribute('name', '')) . '\' function" && git push origin ' . \escapeshellcmd($branchName), '', $stdout, $stderr);
$exit = Console::execute('git config --global user.email "team@appwrite.io" && git config --global user.name "Appwrite" && cd ' . \escapeshellarg($tmpDirectory) . ' && git add . && git commit -m "Create ' . \escapeshellarg($function->getAttribute('name', '')) . ' function" && git push origin ' . \escapeshellarg($branchName), '', $stdout, $stderr);
if ($exit !== 0) {
throw new \Exception('Unable to push code repository: ' . $stderr);
}
$exit = Console::execute('cd ' . $tmpDirectory . ' && git rev-parse HEAD', '', $stdout, $stderr);
$exit = Console::execute('cd ' . \escapeshellarg($tmpDirectory) . ' && git rev-parse HEAD', '', $stdout, $stderr);
if ($exit !== 0) {
throw new \Exception('Unable to get vcs commit SHA: ' . $stderr);
@ -334,7 +388,7 @@ class Builds extends Action
);
}
$tmpPath = '/tmp/builds/' . \escapeshellcmd($buildId);
$tmpPath = '/tmp/builds/' . $buildId;
$tmpPathFile = $tmpPath . '/code.tar.gz';
$localDevice = new Local();
@ -348,21 +402,17 @@ class Builds extends Action
throw new \Exception('Repository directory size should be less than ' . number_format($functionsSizeLimit / 1048576, 2) . ' MBs.');
}
Console::execute('tar --exclude code.tar.gz -czf ' . $tmpPathFile . ' -C /tmp/builds/' . \escapeshellcmd($buildId) . '/code' . (empty($rootDirectory) ? '' : '/' . $rootDirectory) . ' .', '', $stdout, $stderr);
$tarParamDirectory = '/tmp/builds/' . $buildId . '/code' . (empty($rootDirectory) ? '' : '/' . $rootDirectory);
Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $stdout, $stderr); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax
$path = $deviceForFunctions->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
$result = $localDevice->transfer($tmpPathFile, $path, $deviceForFunctions);
$source = $deviceForFunctions->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
$result = $localDevice->transfer($tmpPathFile, $source, $deviceForFunctions);
if (!$result) {
throw new \Exception("Unable to move file");
}
$deployment->setAttribute('path', $path);
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
Console::execute('rm -rf ' . $tmpPath, '', $stdout, $stderr);
$source = $path;
Console::execute('rm -rf ' . \escapeshellarg($tmpPath), '', $stdout, $stderr);
$build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source));
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment->setAttribute('path', $source));

View file

@ -13,7 +13,10 @@ class V18 extends Filter
case 'account.deleteMfaAuthenticator':
unset($content['otp']);
break;
case 'functions.create':
$content['templateVersion'] = $content['templateBranch'];
unset($content['templateBranch']);
break;
}
return $content;

View file

@ -98,9 +98,9 @@ class TemplateFunction extends Model
'default' => '',
'example' => 'appwrite',
])
->addRule('providerBranch', [
->addRule('providerVersion', [
'type' => self::TYPE_STRING,
'description' => 'VCS (Version Control System) branch name',
'description' => 'VCS (Version Control System) branch version (tag).',
'default' => '',
'example' => 'main',
])

View file

@ -250,8 +250,8 @@ class FunctionsCustomClientTest extends Scope
break;
}
if (\microtime(true) - $start > 5) {
$this->fail('Execution did not complete within 5 seconds of schedule');
if (\microtime(true) - $start > 10) {
$this->fail('Execution did not complete within 10 seconds of schedule in status ' . $execution['body']['status'] . ': ' . \json_encode($execution));
}
usleep(500000); // 0.5 seconds
@ -848,6 +848,7 @@ class FunctionsCustomClientTest extends Scope
$this->assertEquals(200, $templates['headers']['status-code']);
$this->assertGreaterThan(0, $templates['body']['total']);
$this->assertIsArray($templates['body']['templates']);
$this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]);
$this->assertArrayHasKey('useCases', $templates['body']['templates'][0]);
for ($i = 0; $i < 25; $i++) {

View file

@ -439,6 +439,135 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals('cli', $functionDetails['body']['type']);
}
public function testCreateDeploymentFromTemplate()
{
$runtimeName = 'php-8.0';
// Fetch starter template (used to create function later)
$template = $this->client->call(Client::METHOD_GET, '/functions/templates/starter', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $template['headers']['status-code']);
$entrypoint = null;
$rootDirectory = null;
$commands = null;
foreach($template['body']['runtimes'] as $runtime) {
if($runtime["name"] !== $runtimeName) {
continue;
}
$entrypoint = $runtime["entrypoint"];
$rootDirectory = $runtime["providerRootDirectory"];
$commands = $runtime["commands"];
break;
}
$this->assertNotNull($entrypoint);
/**
* If below test ever starts failing, it means temaplate used in
* this test now has some variables. This test currently doesnt test variables.
* Remove bellow assertion and update test to crete variable,
* and ensure variable works as expected in execution.
*/
$this->assertEmpty($template['body']['variables']);
// Create function using settings from template.
// Deployment is automatically created from template inside endpoint
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], $this->getHeaders()), [
'functionId' => ID::unique(),
'name' => $template['body']['name'],
'runtime' => $runtimeName,
'execute' => $template['body']['permissions'],
'entrypoint' => $entrypoint,
'events' => $template['body']['events'],
'schedule' => $template['body']['cron'],
'timeout' => $template['body']['timeout'],
'commands' => $commands,
'scopes' => $template['body']['scopes'],
'templateRepository' => $template['body']['providerRepositoryId'],
'templateOwner' => $template['body']['providerOwner'],
'templateRootDirectory' => $rootDirectory,
'templateVersion' => $template['body']['providerVersion'],
]);
$this->assertEquals(201, $function['headers']['status-code']);
$this->assertNotEmpty($function['body']['$id']);
$functionId = $function['body']['$id'];
// List deployments so we can await deployment build
$deployments = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], []);
$this->assertEquals(200, $deployments['headers']['status-code']);
$this->assertEquals(1, $deployments['body']['total']);
$this->assertNotEmpty($deployments['body']['deployments'][0]['$id']);
$deploymentId = $deployments['body']['deployments'][0]['$id'];
// Wait for deployment build to finish
// Deployment is automatically activated
$this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId);
$function = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], $this->getHeaders()), []);
$this->assertEquals(200, $function['headers']['status-code']);
$this->assertEquals($deploymentId, $function['body']['deployment']);
// Execute function to ensure starter code is used
// Also tests if dynamic keys works as expected
$execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'path' => '/ping'
]);
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertEquals("completed", $execution['body']['status']);
$this->assertEquals(200, $execution['body']['responseStatusCode']);
$this->assertEquals("Pong", $execution['body']['responseBody']);
$this->assertEmpty($execution['body']['errors']);
// Get users to ensure execution logged correct total users
$users = $this->client->call(Client::METHOD_GET, '/users', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], $this->getHeaders()), []);
$this->assertEquals(200, $users['headers']['status-code']);
$this->assertIsInt($users['body']['total']);
$totalusers = $users['body']['total'];
$this->assertStringContainsString("Total users: " . $totalusers, $execution['body']['logs']);
// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], []);
$this->assertEquals(204, $response['headers']['status-code']);
}
/**
* @depends testUpdate
*/
@ -1268,7 +1397,11 @@ class FunctionsCustomServerTest extends Scope
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
], $this->getHeaders()), [
'queries' => [
Query::equal('trigger', ['http'])->toString(),
],
]);
$this->assertEquals($executions['headers']['status-code'], 200);
$this->assertEquals($executions['body']['total'], 1);
@ -1284,17 +1417,39 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals($executions['body']['executions'][0]['logs'], '');
$this->assertStringContainsString('timed out', $executions['body']['executions'][0]['errors']);
sleep(75); // Wait for scheduled execution to be created and time out
$start = \microtime(true);
while (true) {
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::equal('trigger', ['schedule'])->toString(),
],
]);
$executions = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/executions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $executions['headers']['status-code']);
if (\count($executions['body']['executions']) > 0) {
break;
}
// 0s would mean instant execution
// +60 seconds, maximum possible waiting time before next minute
// +10 seconds, maximum update interval time
// +60 seconds, possible overlap between update and schedule tick
// +10 seconds, maximum execution time including cold-start
// Result: We allow maximum
if (\microtime(true) - $start > 140) {
$this->fail('Execution did not create within 140 seconds of schedule: ' . \json_encode($executions));
}
usleep(1000000); // 1 second
}
$this->assertEquals(200, $executions['headers']['status-code']);
$this->assertCount(2, $executions['body']['executions']);
$this->assertIsArray($executions['body']['executions']);
$this->assertEquals($executions['body']['executions'][1]['trigger'], 'schedule');
$this->assertGreaterThanOrEqual(1, \count($executions['body']['executions']));
$this->assertEquals($executions['body']['executions'][0]['trigger'], 'schedule');
// Cleanup : Delete function
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [