1
0
Fork 0
mirror of synced 2024-06-27 02:31:04 +12:00

Merge branch 'feat-functions-refactor' of https://github.com/appwrite/appwrite into feat-functions-refactor

This commit is contained in:
Bradley Schofield 2022-01-24 09:37:46 +00:00
commit 9a0b7ce142
20 changed files with 336 additions and 318 deletions

View file

@ -1,10 +1,9 @@
name: "Tests"
on: [pull_request]
jobs:
tests:
name: Unit & E2E
runs-on: self-hosted
runs-on: ubuntu-latest
steps:
- name: Checkout repository
@ -19,16 +18,29 @@ jobs:
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
- name: Build Appwrite
# Upstream bug causes buildkit pulls to fail so prefetch base images
# https://github.com/moby/moby/issues/41864
- name: Prepare Docker
run: |
export COMPOSE_INTERACTIVE_NO_CLI
export DOCKER_BUILDKIT=1
export COMPOSE_DOCKER_CLI_BUILD=1
echo "_APP_FUNCTIONS_RUNTIMES=php-8.0" >> .env
docker pull composer:2.0
docker pull php:8.0-cli-alpine
docker compose build --progress=plain
docker compose pull
- name: Prepare Cache
uses: satackey/action-docker-layer-caching@v0.0.11
# Ignore the failure of a step and avoid terminating the job.
continue-on-error: true
- name: Build Appwrite
run: docker compose build --progress=plain
- name: Start Appwrite
run: |
docker compose up -d
sleep 30
- name: Doctor
run: docker compose exec -T appwrite doctor
@ -37,9 +49,3 @@ jobs:
- name: Run Tests
run: docker compose exec -T appwrite test --debug
- name: Teardown
if: always()
run: |
docker compose down -v
docker ps -aq | xargs docker rm --force

View file

@ -1,29 +0,0 @@
#!/bin/bash bash
RED='\033[0;31m'
NC='\033[0m' # No Color
if [ -z "$1" ]
then
echo "Missing tag number"
exit 1
fi
if [ -z "$2" ]
then
echo "Missing version number"
exit 1
fi
if test $(find "./app/db/DBIP/dbip-country-lite-2021-12.mmdb" -mmin +259200)
then
printf "${RED}GEO country DB has not been updated for more than 6 months. Go to https://db-ip.com/db/download/ip-to-country-lite to download a newer version${NC}\n"
fi
echo 'Starting build...'
docker build --build-arg VERSION="$2" --tag appwrite/appwrite:"$1" .
echo 'Pushing build to registry...'
docker push appwrite/appwrite:"$1"

View file

@ -1 +0,0 @@
echo 'Nothing to deploy right now.'

View file

@ -1,87 +0,0 @@
dist: focal
arch:
- amd64
os: linux
vm:
size: large
language: shell
notifications:
email:
- team@appwrite.io
before_install:
# Install latest Docker
- curl -fsSL https://get.docker.com | sh
# Enable Buildkit in Docker config
- echo '{"experimental":"enabled"}' | sudo tee /etc/docker/daemon.json
- mkdir -p $HOME/.docker
- echo '{"experimental":"enabled"}' | sudo tee $HOME/.docker/config.json
- sudo service docker start
# Login to increase Docker Hub ratelimit
- >
if [ ! -z "${DOCKERHUB_PULL_USERNAME:-}" ]; then
echo "${DOCKERHUB_PULL_PASSWORD}" | docker login --username "${DOCKERHUB_PULL_USERNAME}" --password-stdin
fi
- docker --version
# Install latest Compose
- sudo rm /usr/local/bin/docker-compose
- curl -L https://github.com/docker/compose/releases/download/1.29.2/docker-compose-`uname -s`-`uname -m` > docker-compose
- chmod +x docker-compose
- sudo mv docker-compose /usr/local/bin
- docker-compose --version
# Enable Buildkit
- docker buildx create --name travis_builder --use
- export COMPOSE_INTERACTIVE_NO_CLI
- export DOCKER_BUILDKIT=1
- export COMPOSE_DOCKER_CLI_BUILD=1
- export BUILDKIT_PROGRESS=plain
# Only pass a single runtime for CI stability
- echo "_APP_FUNCTIONS_RUNTIMES=php-8.0" >> .env
# Ensure Travis scripts are executable
- chmod -R u+x ./.travis-ci
install:
- docker-compose pull
# Upstream bug causes buildkit pulls to fail so prefetch base images
# https://github.com/moby/moby/issues/41864
- docker pull composer:2.0
- docker pull php:8.0-cli-alpine
- docker-compose build
- docker-compose up -d
- sleep 60
script:
- docker ps -a
# Tests should fail if any container is in exited status
# - ALL_UP=`docker ps -aq --filter "status=exited"`
# - >
# if [[ "$ALL_UP" != "" ]]; then
# exit 1
# fi
- docker-compose logs appwrite
- docker-compose logs appwrite-realtime
- docker-compose logs mariadb
- docker-compose logs appwrite-worker-functions
- docker-compose exec appwrite doctor
- docker-compose exec appwrite vars
- docker-compose exec appwrite test --debug
after_script:
# travis re-uses their build nodes so clean them up
- docker buildx rm travis_builder
after_failure:
- docker-compose logs appwrite
deploy:
- provider: script
edge: true
script: ./.travis-ci/deploy.sh
on:
repo: appwrite/appwrite
branch: deploy

View file

@ -1,6 +1,8 @@
# Unreleased Version 0.13.0
- Added ability to create syncronous function executions
- Introduced new execution model for functions
- Improved functions execution times
- Improved functions execution times
# Version 0.12.1
## Bugs

View file

@ -24,7 +24,7 @@ COPY public /usr/local/src/public
RUN npm ci
RUN npm run build
FROM php:8.0-cli-alpine as compile
FROM php:8.0.14-cli-alpine as compile
ARG DEBUG=false
ENV DEBUG=$DEBUG
@ -123,7 +123,7 @@ RUN \
./configure && \
make && make install
FROM php:8.0-cli-alpine as final
FROM php:8.0.14-cli-alpine as final
LABEL maintainer="team@appwrite.io"

View file

@ -230,7 +230,7 @@ App::get('/v1/database/collections')
$queries = [];
if (!empty($search)) {
$queries[] = new Query('name', Query::TYPE_SEARCH, [$search]);
$queries[] = new Query('search', Query::TYPE_SEARCH, [$search]);
}
$usage->setParam('database.collections.read', 1);

View file

@ -45,7 +45,7 @@ App::post('/v1/functions')
->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
->param('execute', [], new ArrayList(new Text(64)), 'An array of strings with execution permissions. By default no user is granted with any execute permissions. [learn more about permissions](https://appwrite.io/docs/permissions) and get a full list of available permissions.')
->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.')
->param('vars', new stdClass(), new Assoc(), 'Key-value JSON object that will be passed to the function as environment variables.', true)
->param('vars', [], new Assoc(), 'Key-value JSON object that will be passed to the function as environment variables.', true)
->param('events', [], new ArrayList(new WhiteList(array_keys(Config::getParam('events')), true)), 'Events list.', true)
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
->param('timeout', 15, new Range(1, 900), 'Function maximum execution time in seconds.', true)
@ -863,7 +863,7 @@ App::post('/v1/functions/:functionId/executions')
throw new Exception('Tag not found. Deploy tag before trying to execute a function', 404);
}
$validator = new Authorization($function, 'execute');
$validator = new Authorization('execute');
if (!$validator->isValid($function->getAttribute('execute'))) { // Check if user has write access to execute function
throw new Exception($validator->getDescription(), 401);

View file

@ -25,6 +25,7 @@ use Utopia\Validator\ArrayList;
use Utopia\Validator\JSON;
use Utopia\Validator\Text;
use Cron\CronExpression;
use Swoole\ConnectionPool;
use Utopia\Storage\Device\Local;
use Utopia\Storage\Storage;
use Swoole\Coroutine as Co;
@ -32,6 +33,8 @@ use Utopia\Cache\Cache;
use Utopia\Database\Query;
use Utopia\Orchestration\Adapter\DockerCLI;
use Utopia\Logger\Log;
use Utopia\Orchestration\Adapter\DockerAPI;
use Utopia\Registry\Registry;
require_once __DIR__ . '/init.php';
@ -81,18 +84,22 @@ function logError(Throwable $error, string $action, Utopia\Route $route = null)
Console::error('[Error] Line: ' . $error->getLine());
};
try {
$orchestrationPool = new ConnectionPool(function () {
$dockerUser = App::getEnv('DOCKERHUB_PULL_USERNAME', null);
$dockerPass = App::getEnv('DOCKERHUB_PULL_PASSWORD', null);
$dockerEmail = App::getEnv('DOCKERHUB_PULL_EMAIL', null);
$orchestration = new Orchestration(new DockerCLI($dockerUser, $dockerPass));
return $orchestration;
}, 6);
try {
$runtimes = Config::getParam('runtimes');
// Warmup: make sure images are ready to run fast 🚀
Co\run(function () use ($runtimes, $orchestration) {
Co\run(function () use ($runtimes, $orchestrationPool) {
foreach ($runtimes as $runtime) {
go(function () use ($runtime, $orchestration) {
go(function () use ($runtime, $orchestrationPool) {
$orchestration = $orchestrationPool->get();
Console::info('Warming up ' . $runtime['name'] . ' ' . $runtime['version'] . ' environment...');
$response = $orchestration->pull($runtime['image']);
@ -102,6 +109,8 @@ try {
} else {
Console::warning("Failed to Warmup {$runtime['name']} {$runtime['version']}!");
}
$orchestrationPool->put($orchestration);
});
}
});
@ -113,10 +122,12 @@ try {
$activeFunctions->column('key', Swoole\Table::TYPE_STRING, 4096);
$activeFunctions->create();
Co\run(function () use ($orchestration, $activeFunctions) {
Co\run(function () use ($orchestrationPool, $activeFunctions) {
$orchestration = $orchestrationPool->get();
$executionStart = \microtime(true);
$residueList = $orchestration->list(['label' => 'appwrite-type=function']);
$orchestrationPool->put($orchestration);
foreach ($residueList as $value) {
go(fn () => $activeFunctions->set($value->getName(), [
@ -128,7 +139,6 @@ try {
}
$executionEnd = \microtime(true);
Console::info(count($activeFunctions) . ' functions listed in ' . ($executionEnd - $executionStart) . ' seconds');
});
} catch (\Throwable $error) {
@ -137,164 +147,172 @@ try {
function createRuntimeServer(string $functionId, string $projectId, string $tagId, Database $database): void
{
global $orchestration;
global $orchestrationPool;
global $runtimes;
global $activeFunctions;
$function = $database->getDocument('functions', $functionId);
$tag = $database->getDocument('tags', $tagId);
try {
$orchestration = $orchestrationPool->get();
$function = $database->getDocument('functions', $functionId);
$tag = $database->getDocument('tags', $tagId);
if ($tag->getAttribute('buildId') === null) {
throw new Exception('Tag has no buildId');
}
// Grab Build Document
$build = $database->getDocument('builds', $tag->getAttribute('buildId'));
// Check if function isn't already created
$functions = $orchestration->list(['label' => 'appwrite-type=function', 'name' => 'appwrite-function-' . $tag->getId()]);
if (\count($functions) > 0) {
return;
}
// Generate random secret key
$secret = \bin2hex(\random_bytes(16));
// Check if runtime is active
$runtime = $runtimes[$function->getAttribute('runtime', '')] ?? null;
if ($tag->getAttribute('functionId') !== $function->getId()) {
throw new Exception('Tag not found', 404);
}
if (\is_null($runtime)) {
throw new Exception('Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
}
// Process environment variables
$vars = \array_merge($function->getAttribute('vars', []), [
'APPWRITE_FUNCTION_ID' => $function->getId(),
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name', ''),
'APPWRITE_FUNCTION_TAG' => $tag->getId(),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'],
'APPWRITE_FUNCTION_PROJECT_ID' => $projectId,
'INTERNAL_RUNTIME_KEY' => $secret
]);
$vars = \array_merge($vars, $build->getAttribute('vars', [])); // for gettng endpoint.
$container = 'appwrite-function-' . $tag->getId();
if ($activeFunctions->exists($container) && !(\substr($activeFunctions->get($container)['status'], 0, 2) === 'Up')) { // Remove container if not online
// If container is online then stop and remove it
try {
$orchestration->remove($container, true);
} catch (Exception $e) {
try {
throw new Exception('Failed to remove container: ' . $e->getMessage());
} catch (Throwable $error) {
logError($error, "createRuntimeServer");
}
if ($tag->getAttribute('buildId') === null) {
throw new Exception('Tag has no buildId');
}
$activeFunctions->del($container);
}
// Grab Build Document
$build = $database->getDocument('builds', $tag->getAttribute('buildId'));
// Check if tag hasn't failed
if ($build->getAttribute('status') === 'failed') {
throw new Exception('Tag build failed, please check your logs.', 500);
}
// Check if function isn't already created
$functions = $orchestration->list(['label' => 'appwrite-type=function', 'name' => 'appwrite-function-' . $tag->getId()]);
// Check if tag is built yet.
if ($build->getAttribute('status') !== 'ready') {
throw new Exception('Tag is not built yet', 500);
}
// Grab Tag Files
$tagPath = $build->getAttribute('outputPath', '');
$tagPathTarget = '/tmp/project-' . $projectId . '/' . $build->getId() . '/builtCode/code.tar.gz';
$tagPathTargetDir = \pathinfo($tagPathTarget, PATHINFO_DIRNAME);
$container = 'appwrite-function-' . $tag->getId();
$device = Storage::getDevice('builds');
if (!\file_exists($tagPathTargetDir)) {
if (!\mkdir($tagPathTargetDir, 0777, true)) {
throw new Exception('Can\'t create directory ' . $tagPathTargetDir);
}
}
if (!\file_exists($tagPathTarget)) {
if (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) === Storage::DEVICE_LOCAL) {
if (!\copy($tagPath, $tagPathTarget)) {
throw new Exception('Can\'t create temporary code file ' . $tagPathTarget);
}
} else {
$buffer = $device->read($tagPath);
\file_put_contents($tagPathTarget, $buffer);
}
};
/**
* Limit CPU Usage - DONE
* Limit Memory Usage - DONE
* Limit Network Usage
* Limit Storage Usage (//--storage-opt size=120m \)
* Make sure no access to redis, mariadb, influxdb or other system services
* Make sure no access to NFS server / storage volumes
* Access Appwrite REST from internal network for improved performance
*/
if (!$activeFunctions->exists($container)) { // Create contianer if not ready
$executionStart = \microtime(true);
$executionTime = \time();
$orchestration
->setCpus(App::getEnv('_APP_FUNCTIONS_CPUS', '1'))
->setMemory(App::getEnv('_APP_FUNCTIONS_MEMORY', '256'))
->setSwap(App::getEnv('_APP_FUNCTIONS_MEMORY_SWAP', '256'));
foreach ($vars as $key => $value) {
$vars[$key] = strval($value);
if (\count($functions) > 0) {
return;
}
// Launch runtime server
$id = $orchestration->run(
image: $runtime['image'],
name: $container,
vars: $vars,
labels: [
'appwrite-type' => 'function',
'appwrite-created' => strval($executionTime),
'appwrite-runtime' => $function->getAttribute('runtime', ''),
'appwrite-project' => $projectId,
'appwrite-tag' => $tag->getId(),
],
hostname: $container,
mountFolder: $tagPathTargetDir,
);
// Generate random secret key
$secret = \bin2hex(\random_bytes(16));
if (empty($id)) {
throw new Exception('Failed to create container');
// Check if runtime is active
$runtime = $runtimes[$function->getAttribute('runtime', '')] ?? null;
if ($tag->getAttribute('functionId') !== $function->getId()) {
throw new Exception('Tag not found', 404);
}
// Add to network
$orchestration->networkConnect($container, 'appwrite_runtimes');
if (\is_null($runtime)) {
throw new Exception('Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
}
$executionEnd = \microtime(true);
$activeFunctions->set($container, [
'id' => $id,
'name' => $container,
'status' => 'Up ' . \round($executionEnd - $executionStart, 2) . 's',
'key' => $secret,
// Process environment variables
$vars = \array_merge($function->getAttribute('vars', []), [
'APPWRITE_FUNCTION_ID' => $function->getId(),
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name', ''),
'APPWRITE_FUNCTION_TAG' => $tag->getId(),
'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'],
'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'],
'APPWRITE_FUNCTION_PROJECT_ID' => $projectId,
'INTERNAL_RUNTIME_KEY' => $secret
]);
Console::info('Runtime Server created in ' . ($executionEnd - $executionStart) . ' seconds');
} else {
Console::info('Runtime server is ready to run');
$vars = \array_merge($vars, $build->getAttribute('vars', [])); // for gettng endpoint.
$container = 'appwrite-function-' . $tag->getId();
if ($activeFunctions->exists($container) && !(\substr($activeFunctions->get($container)['status'], 0, 2) === 'Up')) { // Remove container if not online
// If container is online then stop and remove it
try {
$orchestration->remove($container, true);
} catch (Exception $e) {
try {
throw new Exception('Failed to remove container: ' . $e->getMessage());
} catch (Throwable $error) {
logError($error, "createRuntimeServer");
}
}
$activeFunctions->del($container);
}
// Check if tag hasn't failed
if ($build->getAttribute('status') === 'failed') {
throw new Exception('Tag build failed, please check your logs.', 500);
}
// Check if tag is built yet.
if ($build->getAttribute('status') !== 'ready') {
throw new Exception('Tag is not built yet', 500);
}
// Grab Tag Files
$tagPath = $build->getAttribute('outputPath', '');
$tagPathTarget = '/tmp/project-' . $projectId . '/' . $build->getId() . '/builtCode/code.tar.gz';
$tagPathTargetDir = \pathinfo($tagPathTarget, PATHINFO_DIRNAME);
$container = 'appwrite-function-' . $tag->getId();
$device = Storage::getDevice('builds');
if (!\file_exists($tagPathTargetDir)) {
if (@\mkdir($tagPathTargetDir, 0777, true)) {
\chmod($tagPathTargetDir, 0777);
} else {
throw new Exception('Can\'t create directory ' . $tagPathTargetDir);
}
}
if (!\file_exists($tagPathTarget)) {
if (App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL) === Storage::DEVICE_LOCAL) {
if (!\copy($tagPath, $tagPathTarget)) {
throw new Exception('Can\'t create temporary code file ' . $tagPathTarget);
}
} else {
$buffer = $device->read($tagPath);
\file_put_contents($tagPathTarget, $buffer);
}
};
/**
* Limit CPU Usage - DONE
* Limit Memory Usage - DONE
* Limit Network Usage
* Limit Storage Usage (//--storage-opt size=120m \)
* Make sure no access to redis, mariadb, influxdb or other system services
* Make sure no access to NFS server / storage volumes
* Access Appwrite REST from internal network for improved performance
*/
if (!$activeFunctions->exists($container)) { // Create contianer if not ready
$executionStart = \microtime(true);
$executionTime = \time();
$orchestration
->setCpus(App::getEnv('_APP_FUNCTIONS_CPUS', '1'))
->setMemory(App::getEnv('_APP_FUNCTIONS_MEMORY', '256'))
->setSwap(App::getEnv('_APP_FUNCTIONS_MEMORY_SWAP', '256'));
$vars = array_map(fn ($v) => strval($v), $vars);
// Launch runtime server
$id = $orchestration->run(
image: $runtime['image'],
name: $container,
vars: $vars,
labels: [
'appwrite-type' => 'function',
'appwrite-created' => strval($executionTime),
'appwrite-runtime' => $function->getAttribute('runtime', ''),
'appwrite-project' => $projectId,
'appwrite-tag' => $tag->getId(),
],
hostname: $container,
mountFolder: $tagPathTargetDir,
);
if (empty($id)) {
throw new Exception('Failed to create container');
}
// Add to network
$orchestration->networkConnect($container, 'appwrite_runtimes');
$executionEnd = \microtime(true);
$activeFunctions->set($container, [
'id' => $id,
'name' => $container,
'status' => 'Up ' . \round($executionEnd - $executionStart, 2) . 's',
'key' => $secret,
]);
Console::info('Runtime Server created in ' . ($executionEnd - $executionStart) . ' seconds');
} else {
Console::info('Runtime server is ready to run');
}
} catch (\Throwable $th) {
$orchestrationPool->put($orchestration);
throw $th;
} finally {
$orchestrationPool->put($orchestration);
}
};
@ -410,7 +428,7 @@ function execute(string $trigger, string $projectId, string $executionId, string
$database->updateDocument('tags', $tag->getId(), $tag);
runBuildStage($buildId, $projectId, $database);
runBuildStage($buildId, $projectId);
}
} catch (Exception $e) {
$execution
@ -671,8 +689,11 @@ App::post('/v1/cleanup/function')
->inject('response')
->inject('dbForProject')
->action(
function (string $functionId, Response $response, Database $dbForProject) use ($orchestration) {
function (string $functionId, Response $response, Database $dbForProject) use ($orchestrationPool) {
try {
/** @var Orchestration $orchestration */
$orchestration = $orchestrationPool->get();
// Get function document
$function = $dbForProject->getDocument('functions', $functionId);
@ -712,9 +733,11 @@ App::post('/v1/cleanup/function')
return $response->json(['success' => true]);
} catch (Exception $e) {
logError($e, "cleanupFunction");
$orchestrationPool->put($orchestration);
return $response->json(['error' => $e->getMessage()]);
}
$orchestrationPool->put($orchestration);
}
);
@ -722,8 +745,11 @@ App::post('/v1/cleanup/tag')
->param('tagId', '', new UID(), 'Tag unique ID.')
->inject('response')
->inject('dbForProject')
->action(function (string $tagId, Response $response, Database $dbForProject) use ($orchestration) {
->action(function (string $tagId, Response $response, Database $dbForProject) use ($orchestrationPool) {
try {
/** @var Orchestration $orchestration */
$orchestration = $orchestrationPool->get();
// Get tag document
$tag = $dbForProject->getDocument('tags', $tagId);
@ -752,8 +778,11 @@ App::post('/v1/cleanup/tag')
}
} catch (Exception $e) {
logError($e, "cleanupFunction");
$orchestrationPool->put($orchestration);
return $response->json(['error' => $e->getMessage()]);
}
$orchestrationPool->put($orchestration);
return $response->json(['success' => true]);
});
@ -765,7 +794,8 @@ App::post('/v1/tag')
->inject('response')
->inject('dbForProject')
->inject('projectID')
->action(function (string $functionId, string $tagId, string $userId, Response $response, Database $dbForProject, string $projectID) use ($runtimes) {
->inject('register')
->action(function (string $functionId, string $tagId, string $userId, Response $response, Database $dbForProject, string $projectID, Registry $register) use ($runtimes) {
// Get function document
$function = $dbForProject->getDocument('functions', $functionId);
// Get tag document
@ -826,9 +856,16 @@ App::post('/v1/tag')
}
// Build Code
go(function () use ($dbForProject, $projectID, $tagId, $buildId, $functionId, $function) {
go(function () use ($projectID, $tagId, $buildId, $functionId, $function, $register) {
$db = $register->get('dbPool')->get();
$redis = $register->get('redisPool')->get();
$cache = new Cache(new RedisCache($redis));
$dbForProject = new Database(new MariaDB($db), $cache);
$dbForProject->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$dbForProject->setNamespace('_project_' . $projectID);
// Build Code
runBuildStage($buildId, $projectID, $dbForProject);
runBuildStage($buildId, $projectID);
// Update the schedule
$schedule = $function->getAttribute('schedule', '');
@ -856,6 +893,9 @@ App::post('/v1/tag')
// Deploy Runtime Server
createRuntimeServer($functionId, $projectID, $tagId, $dbForProject);
$register->get('dbPool')->put($db);
$register->get('redisPool')->put($redis);
});
if (false === $function) {
@ -867,7 +907,8 @@ App::post('/v1/tag')
App::get('/v1/')
->inject('response')
->action(function (Response $response) {
->action(
function (Response $response) {
$response
->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate')
->addHeader('Expires', '0')
@ -920,12 +961,15 @@ App::post('/v1/build/:buildId') // Start a Build
}
});
function runBuildStage(string $buildId, string $projectID, Database $database): Document
function runBuildStage(string $buildId, string $projectID): Document
{
global $runtimes;
global $orchestration;
global $orchestrationPool;
global $register;
/** @var Orchestration $orchestration */
$orchestration = $orchestrationPool->get();
$buildStdout = '';
$buildStderr = '';
@ -971,7 +1015,9 @@ function runBuildStage(string $buildId, string $projectID, Database $database):
// Perform various checks
if (!\file_exists($tagPathTargetDir)) {
if (!\mkdir($tagPathTargetDir, 0777, true)) {
if (@\mkdir($tagPathTargetDir, 0777, true)) {
\chmod($tagPathTargetDir, 0777);
} else {
throw new Exception('Can\'t create directory ' . $tagPathTargetDir);
}
}
@ -1002,12 +1048,13 @@ function runBuildStage(string $buildId, string $projectID, Database $database):
->setMemory(App::getEnv('_APP_FUNCTIONS_MEMORY', 256))
->setSwap(App::getEnv('_APP_FUNCTIONS_MEMORY_SWAP', 256));
foreach ($vars as &$value) {
$value = strval($value);
}
$vars = array_map(fn ($v) => strval($v), $vars);
$path = '/tmp/project-' . $projectID . '/' . $build->getId() . '/builtCode';
if (!\file_exists('/tmp/project-' . $projectID . '/' . $build->getId() . '/builtCode')) {
if (!\mkdir('/tmp/project-' . $projectID . '/' . $build->getId() . '/builtCode', 0777, true)) {
if (!\file_exists($path)) {
if (@\mkdir($path, 0777, true)) {
\chmod($path, 0777);
} else {
throw new Exception('Can\'t create directory /tmp/project-' . $projectID . '/' . $build->getId() . '/builtCode');
}
};
@ -1108,7 +1155,9 @@ function runBuildStage(string $buildId, string $projectID, Database $database):
$path = $device->getPath(\uniqid() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
if (!\file_exists(\dirname($path))) { // Checks if directory path to file exists
if (!@\mkdir(\dirname($path), 0777, true)) {
if (@\mkdir(\dirname($path), 0777, true)) {
\chmod(\dirname($path), 0777);
} else {
throw new Exception('Can\'t create directory: ' . \dirname($path));
}
}
@ -1149,12 +1198,18 @@ function runBuildStage(string $buildId, string $projectID, Database $database):
$build = $database->updateDocument('builds', $buildId, $build);
// also remove the container if it exists
if ($id) {
if (isset($id)) {
$orchestration->remove($id, true);
}
$orchestrationPool->put($orchestration);
$register->get('dbPool')->put($db);
$register->get('redisPool')->put($redis);
throw new Exception('Build failed: ' . $e->getMessage());
} finally {
$orchestrationPool->put($orchestration);
$register->get('dbPool')->put($db);
$register->get('redisPool')->put($redis);
}
@ -1166,8 +1221,9 @@ App::setMode(App::MODE_TYPE_PRODUCTION); // Define Mode
$http = new Server("0.0.0.0", 8080);
function handleShutdown() {
global $orchestration;
function handleShutdown()
{
global $orchestrationPool;
global $register;
try {
@ -1175,6 +1231,8 @@ function handleShutdown() {
// Remove all containers.
/** @var Orchestration $orchestration */
$orchestration = $orchestrationPool->get();
$functionsToRemove = $orchestration->list(['label' => 'appwrite-type=function']);
@ -1210,6 +1268,8 @@ function handleShutdown() {
}
} catch (\Throwable $error) {
logError($error, 'shutdownError');
} finally {
$orchestrationPool->put($orchestration);
}
};

View file

@ -620,6 +620,7 @@ $logs = $this->getParam('logs', null);
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@reset="array = required = false"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
@ -675,6 +676,7 @@ $logs = $this->getParam('logs', null);
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@reset="array = required = false"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
@ -739,6 +741,7 @@ $logs = $this->getParam('logs', null);
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@reset="array = required = false"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
@ -803,6 +806,7 @@ $logs = $this->getParam('logs', null);
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@reset="array = required = false"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
@ -855,6 +859,7 @@ $logs = $this->getParam('logs', null);
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@reset="array = required = false"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
@ -911,6 +916,7 @@ $logs = $this->getParam('logs', null);
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@reset="array = required = false"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
@ -963,6 +969,7 @@ $logs = $this->getParam('logs', null);
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@reset="array = required = false"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />
@ -1015,6 +1022,7 @@ $logs = $this->getParam('logs', null);
data-failure="alert"
data-failure-param-alert-text="Failed to create attribute"
data-failure-param-alert-classname="error"
@reset="array = required = false"
x-data="{ array: false, required: false }">
<input type="hidden" name="projectId" data-ls-bind="{{router.params.project}}" />

View file

@ -174,7 +174,7 @@ $smtpEnabled = $this->getParam('smtpEnabled', false);
<input type="email" class="full-width" id="user-email" name="email" required autocomplete="off" />
<label for="user-password">Password</label>
<input type="password" class="full-width" id="user-password" name="password" required pattern=".{6,}" title="Six or more characters" autocomplete="off" />
<input type="password" class="full-width" id="user-password" name="password" required minlength="8" title="Eight or more characters" autocomplete="off" />
<hr />

View file

@ -28,10 +28,10 @@
<input type="hidden" name="secret" data-ls-bind="{{router.params.secret}}">
<label>Password</label>
<input name="password" type="password" autocomplete="off" placeholder="" required data-forms-password-meter pattern=".{6,}" title="Six or more characters">
<input name="password" type="password" autocomplete="off" placeholder="" required data-forms-password-meter minlength="8" title="Eight or more characters">
<label>Confirm Password</label>
<input name="passwordAgain" type="password" autocomplete="off" placeholder="" required data-forms-password-meter pattern=".{6,}" title="Six or more characters">
<input name="passwordAgain" type="password" autocomplete="off" placeholder="" required data-forms-password-meter minlength="8" title="Eight or more characters">
<button type="submit" class="btn btn-primary"><i class="fa fa-sign-in"></i> Apply</button>
</form>

View file

@ -37,7 +37,7 @@ $root = ($this->getParam('root') !== 'disabled');
<input name="email" type="email" class="full-width" autocomplete="email" placeholder="Email" required>
<input name="password" type="password" class="full-width" autocomplete="off" placeholder="Password" required pattern=".{6,}" title="Six or more characters">
<input name="password" type="password" class="full-width" autocomplete="off" placeholder="Password" required minlength="8" title="Eight or more characters">
<button>Sign In</button>
</form>

View file

@ -46,7 +46,7 @@ $root = ($this->getParam('root') !== 'disabled');
<input name="email" type="email" autocomplete="email" placeholder="" required data-ls-bind="{{router.params.email}}">
<label>Password</label>
<input name="password" type="password" autocomplete="off" placeholder="" required data-forms-password-meter pattern=".{8,}" title="Eight or more characters">
<input name="password" type="password" autocomplete="off" placeholder="" required data-forms-password-meter minlength="8" title="Eight or more characters">
<div class="agree margin-top-large margin-bottom-large">
<div class="pull-start margin-end-small margin-bottom">

View file

@ -72,7 +72,7 @@
"require-dev": {
"appwrite/sdk-generator": "0.17.1",
"phpunit/phpunit": "9.5.10",
"swoole/ide-helper": "4.8.3",
"swoole/ide-helper": "4.8.5",
"textalk/websocket": "1.5.5",
"vimeo/psalm": "4.13.1"
},

14
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": "adf8727742248da9d7143546e513f96d",
"content-hash": "cba39f50398d5ae2b121db34c9e4c529",
"packages": [
{
"name": "adhocore/jwt",
@ -5676,16 +5676,16 @@
},
{
"name": "swoole/ide-helper",
"version": "4.8.3",
"version": "4.8.5",
"source": {
"type": "git",
"url": "https://github.com/swoole/ide-helper.git",
"reference": "3ac4971814273889933b871e03b2a6b340e58f79"
"reference": "d03c707d4dc803228e93b4884c72949c4d28e8b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/swoole/ide-helper/zipball/3ac4971814273889933b871e03b2a6b340e58f79",
"reference": "3ac4971814273889933b871e03b2a6b340e58f79",
"url": "https://api.github.com/repos/swoole/ide-helper/zipball/d03c707d4dc803228e93b4884c72949c4d28e8b8",
"reference": "d03c707d4dc803228e93b4884c72949c4d28e8b8",
"shasum": ""
},
"type": "library",
@ -5702,7 +5702,7 @@
"description": "IDE help files for Swoole.",
"support": {
"issues": "https://github.com/swoole/ide-helper/issues",
"source": "https://github.com/swoole/ide-helper/tree/4.8.3"
"source": "https://github.com/swoole/ide-helper/tree/4.8.5"
},
"funding": [
{
@ -5714,7 +5714,7 @@
"type": "github"
}
],
"time": "2021-12-01T08:11:40+00:00"
"time": "2021-12-24T22:44:20+00:00"
},
{
"name": "symfony/console",

View file

@ -1 +1 @@
Use this endpoint to log out the currently logged in user from all their account sessions across all of their different devices. When using the option id argument, only the session unique ID provider will be deleted.
Use this endpoint to log out the currently logged in user from all their account sessions across all of their different devices. When using the Session ID argument, only the unique session ID provided is deleted.

View file

@ -33,7 +33,7 @@ abstract class Scope extends TestCase
protected function getLastEmail():array
{
sleep(5);
sleep(3);
$emails = json_decode(file_get_contents('http://maildev:1080/email'), true);
@ -46,7 +46,7 @@ abstract class Scope extends TestCase
protected function getLastRequest():array
{
sleep(5);
sleep(2);
$resquest = json_decode(file_get_contents('http://request-catcher:5000/__last_request__'), true);
$resquest['data'] = json_decode($resquest['data'], true);
@ -167,4 +167,4 @@ abstract class Scope extends TestCase
return self::$user[$this->getProject()['$id']];
}
}
}

View file

@ -125,6 +125,39 @@ class DatabaseCustomServerTest extends Scope
$this->assertCount(0, $collections['body']['collections']);
$this->assertEmpty($collections['body']['collections']);
/**
* Test for Search
*/
$collections = $this->client->call(Client::METHOD_GET, '/database/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => 'first'
]);
$this->assertEquals(1, $collections['body']['sum']);
$this->assertEquals('first', $collections['body']['collections'][0]['$id']);
$collections = $this->client->call(Client::METHOD_GET, '/database/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => 'Test'
]);
$this->assertEquals(2, $collections['body']['sum']);
$this->assertEquals('Test 1', $collections['body']['collections'][0]['name']);
$this->assertEquals('Test 2', $collections['body']['collections'][1]['name']);
$collections = $this->client->call(Client::METHOD_GET, '/database/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'search' => 'Nonexistent'
]);
$this->assertEquals(0, $collections['body']['sum']);
/**
* Test for FAILURE
*/

View file

@ -224,6 +224,32 @@ class FunctionsCustomClientTest extends Scope
];
}
public function testCreateExecutionUnauthorized():array
{
$function = $this->client->call(Client::METHOD_POST, '/functions', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey'],
], [
'functionId' => 'unique()',
'name' => 'Test',
'execute' => [],
'runtime' => 'php-8.0',
'timeout' => 10,
]);
$execution = $this->client->call(Client::METHOD_POST, '/functions/'.$function['body']['$id'].'/executions', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], [
'async' => 1,
]);
$this->assertEquals(401, $execution['headers']['status-code']);
return [];
}
/**
* @depends testCreateCustomExecution
*/