Compare commits

...
Author SHA1 Message Date
Matej Bačo 02b7e0df4d queries backwards compatibilit 2025-05-18 22:51:17 +02:00
Matej Bačo e84ad1e83c Upgrade libs 2025-05-18 22:37:30 +02:00
Matej Bačo 0e90f64ea6 Fix backwards compatibility for function reation 2025-05-18 22:07:52 +02:00
Matej BačoandGitHub 08e93997b5 Merge pull request #9802 from appwrite/1.7.x
Fix: domain validaiton
2025-05-18 21:45:35 +02:00
Matej BačoandGitHub aaee95a761 Merge branch 'main' into 1.7.x 2025-05-18 21:39:53 +02:00
Matej BačoandGitHub 637b842e16 Merge pull request #9801 from appwrite/fix-request-response-1.7.x
fix: request response formats for 1.7.x
2025-05-18 21:32:36 +02:00
Matej Bačo 1e24d14dd1 Fix DNS lookup speed 2025-05-18 21:32:26 +02:00
Chirag AggarwalandGitHub 2fe80462f1 Merge branch '1.7.x' into fix-request-response-1.7.x 2025-05-19 00:35:40 +05:30
Chirag Aggarwal 2ba4ae8e60 fix: request response formats for 1.7.x 2025-05-18 18:54:29 +00:00
Matej Bačo 8170661012 Fix domain validaiton 2025-05-18 20:51:52 +02:00
Jake BarnbyandGitHub 9873daa3ad Merge pull request #9649 from appwrite/1.7.x
1.7.x
2025-05-18 15:15:37 +00:00
Jake BarnbyandGitHub 33ac93338d Merge pull request #9798 from appwrite/chore-bump-console
Update console
2025-05-18 14:32:15 +00:00
Jake Barnby 3e2afc468a Update console 2025-05-19 02:31:02 +12:00
Jake BarnbyandGitHub cb9f45a546 Merge pull request #9797 from appwrite/chore-review-fixes
Chore review fixes
2025-05-18 14:08:18 +00:00
Jake Barnby bb7055fb47 Lint 2025-05-19 01:59:02 +12:00
Jake Barnby 4307bb8b33 Fix deprecated method usage 2025-05-19 01:56:25 +12:00
Jake Barnby dd4ffa41af Fix header check mismatch 2025-05-19 01:56:08 +12:00
Jake Barnby 5a6e891099 Fix invalid matrix read 2025-05-19 01:56:00 +12:00
Jake BarnbyandGitHub abba19a22f Merge branch 'main' into 1.7.x 2025-05-18 13:27:53 +00:00
Christy JacobandGitHub 2db4086ec3 Merge pull request #9796 from appwrite/sync-1.6.x
Sync 1.6.x
2025-05-18 15:24:32 +02:00
Matej BačoandGitHub 0d58bb8ede Merge pull request #9788 from appwrite/fix-remove-builds
fix: remove builds
2025-05-18 14:47:08 +02:00
Jake BarnbyandGitHub b06e4501c5 Merge branch 'main' into 1.7.x 2025-05-18 12:46:06 +00:00
Jake Barnby 7c6c9d7cb2 Merge remote-tracking branch 'origin/1.6.x' into sync-1.6.x 2025-05-19 00:44:50 +12:00
Jake BarnbyandGitHub 5b9bfd7673 Merge pull request #9795 from appwrite/fix-version
Update version
2025-05-18 12:43:29 +00:00
Jake Barnby 41668d4cd0 Update version 2025-05-19 00:41:37 +12:00
Jake BarnbyandGitHub d0bb88f69f Merge pull request #9793 from appwrite/fix-migration-version
Fix class name
2025-05-18 11:56:17 +00:00
Matej BačoandGitHub 413bfebd5d Merge branch '1.7.x' into fix-remove-builds 2025-05-18 13:13:35 +02:00
Matej Bačo 2bee98afcf Fix remaining tests 2025-05-18 12:51:07 +02:00
Matej Bačo b228bdd9c7 Fix failing tests 2025-05-18 12:02:32 +02:00
Matej Bačo 0e69929054 Fix separation strategy 2025-05-18 11:39:30 +02:00
Matej Bačo 2ae33a7351 Merge branch 'fix-remove-builds' of github.com:appwrite/appwrite into fix-remove-builds 2025-05-18 11:08:26 +02:00
Matej Bačo 9b4f787d40 Fix edge case with no commands 2025-05-18 11:08:14 +02:00
Matej BačoandGitHub 26c69604d7 Merge branch '1.7.x' into fix-remove-builds 2025-05-18 10:49:37 +02:00
Matej Bačo 03f01e322e SSR detection without createCommand and remove=false 2025-05-18 10:30:27 +02:00
loks0n 27a5bd70bc fix: remove builds 2025-05-18 00:56:11 +01:00
Torsten DittmannandGitHub ac77c15a59 Merge pull request #9771 from appwrite/LauraDuRy-patch-2
Update README.md
2025-05-16 18:17:19 +02:00
Laura Du RyandGitHub 4417306295 Update README.md 2025-05-16 09:41:09 +02:00
Steven NguyenandGitHub fa2a3bad53 Merge pull request #9747 from appwrite/generate-server-sdk
feat: add ability to generate client and server sdks
2025-05-15 08:28:32 -07:00
Chirag Aggarwal f818607680 chore: keep console sdk preview automatic 2025-05-15 05:27:43 +00:00
Chirag Aggarwal e1e6b82ca3 chore: remove on pull request 2025-05-15 05:22:48 +00:00
Chirag Aggarwal 403a2e9db3 chore: whitespace 2025-05-15 05:17:57 +00:00
Chirag Aggarwal 1c5f7ccd37 chore: update sdk preview workflow to be generic 2025-05-15 05:14:39 +00:00
Steven NguyenandGitHub aa411e4ac8 Merge pull request #9755 from appwrite/release-1.6.2
Sync 1.6.2 into main
2025-05-14 17:10:19 -07:00
26 changed files with 383 additions and 103 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ _APP_OPENSSL_KEY_V1=your-secret-key
_APP_DOMAIN=traefik
_APP_DOMAIN_FUNCTIONS=functions.localhost
_APP_DOMAIN_SITES=sites.localhost
_APP_DOMAIN_TARGET_CNAME=test.appwrite.io
_APP_DOMAIN_TARGET_CNAME=test.localhost
_APP_DOMAIN_TARGET_A=127.0.0.1
_APP_DOMAIN_TARGET_AAAA=::1
_APP_RULES_FORMAT=md5
+26 -5
View File
@@ -1,25 +1,46 @@
name: "Console SDK Preview"
name: "SDK Preview"
on:
pull_request:
paths:
- 'app/config/specs/*-latest-console.json'
workflow_dispatch:
inputs:
platform:
type: choice
description: "Platform to build"
options:
- client
- server
jobs:
setup:
name: Setup & Build Console SDK
name: Setup & Build SDK
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set SDK type
id: set-sdk
run: |
PLATFORM="${{ github.event.inputs.platform }}"
if [ -z "$PLATFORM" ]; then
PLATFORM="console"
fi
if [ "$PLATFORM" = "server" ]; then
echo "sdk_type=nodejs" >> $GITHUB_OUTPUT
else
echo "sdk_type=web" >> $GITHUB_OUTPUT
fi
echo "platform=$PLATFORM" >> $GITHUB_OUTPUT
- name: Load and Start Appwrite
run: |
docker compose build
docker compose up -d
docker compose exec appwrite sdks --platform=console --sdk=web --version=latest --git=no
sudo chown -R $USER:$USER ./app/sdks/console-web
docker compose exec appwrite sdks --platform=${{ steps.set-sdk.outputs.platform }} --sdk=${{ steps.set-sdk.outputs.sdk_type }} --version=latest --git=no
sudo chown -R $USER:$USER ./app/sdks/${{ steps.set-sdk.outputs.platform }}-${{ steps.set-sdk.outputs.sdk_type }}
- uses: actions/setup-node@v4
with:
+1 -1
View File
@@ -303,7 +303,7 @@ jobs:
docker compose up -d
sleep 30
- name: Run Projects tests with dev keys in ${{ matrix.tables-mode }} table mode
- name: Run Projects tests with dev keys in dedicated table mode
run: |
echo "Using project tables"
export _APP_DATABASE_SHARED_TABLES=
+3 -3
View File
@@ -72,7 +72,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.6.2
appwrite/appwrite:1.7.0
```
### Windows
@@ -84,7 +84,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.6.2
appwrite/appwrite:1.7.0
```
#### PowerShell
@@ -94,7 +94,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.6.2
appwrite/appwrite:1.7.0
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
+4 -3
View File
@@ -1,4 +1,5 @@
> [Get started with Appwrite](https://apwr.dev/appcloud)
> [Join the Init kick off event 19th of May: The future of Appwrite with Founder & CEO Eldad Fux](https://www.youtube.com/watch?v=1g8tuogsp7A)
<br />
<p align="center">
@@ -77,7 +78,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.6.2
appwrite/appwrite:1.7.0
```
### Windows
@@ -89,7 +90,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.6.2
appwrite/appwrite:1.7.0
```
#### PowerShell
@@ -99,7 +100,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.6.2
appwrite/appwrite:1.7.0
```
Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation.
+1 -1
View File
@@ -216,7 +216,7 @@ App::patch('/v1/proxy/rules/:ruleId/verification')
$validators = [];
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
if (!$targetCNAME->isKnown() || $targetCNAME->isTest()) {
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
}
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
+2 -2
View File
@@ -37,8 +37,8 @@ const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours
const APP_RESOURCE_TOKEN_ACCESS = 24 * 60 * 60; // 24 hours
const APP_FILE_ACCESS = 24 * 60 * 60; // 24 hours
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
const APP_CACHE_BUSTER = 4318;
const APP_VERSION_STABLE = '1.7.0-RC1';
const APP_CACHE_BUSTER = 4319;
const APP_VERSION_STABLE = '1.7.0';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
+2 -2
View File
@@ -815,7 +815,7 @@ App::setResource('devKey', function (Request $request, Document $project, array
// add sdk to key
$sdkValidator = new WhiteList($servers, true);
$sdk = $request->getHeader('x-sdk-name', 'UNKNOWN');
$sdk = \strtolower($request->getHeader('x-sdk-name', 'UNKNOWN'));
if ($sdk !== 'UNKNOWN' && $sdkValidator->isValid($sdk)) {
$sdks = $key->getAttribute('sdks', []);
@@ -901,7 +901,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
$tokenJWT = $request->getParam('token');
if (!empty($tokenJWT) && !$project->isEmpty()) { // JWT authentication
$jwt = new JWT(App::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
try {
$payload = $jwt->decode($tokenJWT);
+1 -1
View File
@@ -177,7 +177,7 @@ $image = $this->getParam('image', '');
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: <?php echo $organization; ?>/console:6.0.0
image: <?php echo $organization; ?>/console:6.0.1
restart: unless-stopped
networks:
- appwrite
+1 -1
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.*",
Generated
+19 -18
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"
}
+1 -1
View File
@@ -213,7 +213,7 @@ services:
appwrite-console:
<<: *x-logging
container_name: appwrite-console
image: appwrite/console:6.0.0
image: appwrite/console:6.0.1
restart: unless-stopped
networks:
- appwrite
@@ -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
@@ -659,6 +659,28 @@ class Builds extends Action
if ($version === 'v2') {
$command = 'tar -zxf /tmp/code.tar.gz -C /usr/code && cd /usr/local/src/ && ./build.sh';
} else {
if ($resource->getCollection() === 'sites') {
$listFilesCommand = '';
// Start separation, enter build folder
$listFilesCommand .= 'echo "{APPWRITE_DETECTION_SEPARATOR_START}" && cd /usr/local/build';
// Enter output directory, if set
if (!empty($resource->getAttribute('outputDirectory', ''))) {
$listFilesCommand .= ' && cd ' . \escapeshellarg($resource->getAttribute('outputDirectory', ''));
}
// Print files, and end separation
$listFilesCommand .= ' && find . -name \'node_modules\' -prune -o -type f -print && echo "{APPWRITE_DETECTION_SEPARATOR_END}"';
// Use SSR file listing
if (empty($command)) {
$command = $listFilesCommand;
} else {
$command .= ' && ' . $listFilesCommand;
}
}
$command = 'tar -zxf /tmp/code.tar.gz -C /mnt/code && helpers/build.sh ' . \trim(\escapeshellarg($command));
}
@@ -671,7 +693,7 @@ class Builds extends Action
cpus: $cpus,
memory: $memory,
timeout: $timeout,
remove: false,
remove: true,
entrypoint: $deployment->getAttribute('entrypoint', ''),
destination: APP_STORAGE_BUILDS . "/app-{$project->getId()}",
variables: $vars,
@@ -684,11 +706,13 @@ class Builds extends Action
}),
Co\go(function () use ($executor, $project, &$deployment, &$response, $dbForProject, $timeout, &$err, $queueForRealtime, &$isCanceled) {
try {
$insideSeparation = false;
$executor->getLogs(
deploymentId: $deployment->getId(),
projectId: $project->getId(),
timeout: $timeout,
callback: function ($logs) use (&$response, &$err, $dbForProject, &$isCanceled, &$deployment, $queueForRealtime) {
callback: function ($logs) use (&$response, &$err, $dbForProject, &$isCanceled, &$deployment, $queueForRealtime, &$insideSeparation) {
if ($isCanceled) {
return;
}
@@ -706,7 +730,28 @@ class Builds extends Action
// Get only valid UTF8 part - removes leftover half-multibytes causing SQL errors
$logs = \mb_substr($logs, 0, null, 'UTF-8');
// Do not stream logs added for SSR detection
if (!$insideSeparation) {
$separator = \strpos($logs, '{APPWRITE_DETECTION_SEPARATOR_START}');
if ($separator !== false) {
$logs = \substr($logs, 0, $separator);
$insideSeparation = true;
}
} else {
$logs = '';
$separator = \strpos($logs, '{APPWRITE_DETECTION_SEPARATOR_END}');
if ($separator !== false) {
$logs = \substr($logs, $separator + strlen('{APPWRITE_DETECTION_SEPARATOR_END}'));
$insideSeparation = false;
}
}
if (empty($logs)) {
return;
}
$currentLogs = $deployment->getAttribute('buildLogs', '');
$affected = false;
$streamLogs = \str_replace("\\n", "{APPWRITE_LINEBREAK_PLACEHOLDER}", $logs);
foreach (\explode("\n", $streamLogs) as $streamLog) {
@@ -719,14 +764,20 @@ class Builds extends Action
// TODO: use part[0] as timestamp when switching to dbForLogs for build logs
$currentLogs .= $streamParts[1];
if (!empty($streamParts[1])) {
$affected = true;
}
}
$deployment = $deployment->setAttribute('buildLogs', $currentLogs);
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
if ($affected) {
$deployment = $deployment->setAttribute('buildLogs', $currentLogs);
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
$queueForRealtime
->setPayload($deployment->getArrayCopy())
->trigger();
$queueForRealtime
->setPayload($deployment->getArrayCopy())
->trigger();
}
}
}
);
@@ -755,44 +806,6 @@ class Builds extends Action
throw new \Exception('Build size should be less than ' . number_format($buildSizeLimit / 1048576, 2) . ' MBs.');
}
if ($resource->getCollection() === 'sites') {
// TODO: Refactor with structured command in future, using utopia library (CLI)
$listFilesCommand = "cd /usr/local/build && cd " . \escapeshellarg($resource->getAttribute('outputDirectory', './')) . " && find . -name 'node_modules' -prune -o -type f -print";
$command = $executor->createCommand(
deploymentId: $deployment->getId(),
projectId: $project->getId(),
command: $listFilesCommand,
timeout: 15
);
$files = \explode("\n", $command['output']); // Parse output
$files = \array_filter($files); // Remove empty
$files = \array_map(fn ($file) => \trim($file), $files); // Remove whitepsaces
$files = \array_map(fn ($file) => \str_starts_with($file, './') ? \substr($file, 2) : $file, $files); // Remove beginning ./
$detector = new Rendering($files, $resource->getAttribute('framework', ''));
$detector
->addOption(new SSR())
->addOption(new XStatic());
$detection = $detector->detect();
$adapter = $resource->getAttribute('adapter', '');
if (empty($adapter)) {
$resource->setAttribute('adapter', $detection->getName());
$resource->setAttribute('fallbackFile', $detection->getFallbackFile() ?? '');
$resource = $dbForProject->updateDocument('sites', $resource->getId(), $resource);
$deployment->setAttribute('adapter', $detection->getName());
$deployment->setAttribute('fallbackFile', $detection->getFallbackFile() ?? '');
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
} elseif ($adapter === 'ssr' && $detection->getName() === 'static') {
throw new \Exception('Adapter mismatch. Detected: ' . $detection->getName() . ' does not match with the set adapter: ' . $adapter);
}
}
$executor->deleteRuntime($project->getId(), $deployment->getId(), '-build');
/** Update the build document */
$deployment->setAttribute('buildStartedAt', DateTime::format((new \DateTime())->setTimestamp(floor($response['startTime']))));
$deployment->setAttribute('buildEndedAt', $endTime);
@@ -806,6 +819,17 @@ class Builds extends Action
$logs .= $log['content'];
}
// Separate logs for SSR detection
$detectionLogs = '';
$separator = \strpos($logs, '{APPWRITE_DETECTION_SEPARATOR_START}');
if ($separator !== false) {
$detectionLogs = \substr($logs, $separator + strlen('{APPWRITE_DETECTION_SEPARATOR}'));
$separatorEnd = \strpos($detectionLogs, '{APPWRITE_DETECTION_SEPARATOR_END}');
$logs .= \substr($detectionLogs, $separatorEnd + strlen('{APPWRITE_DETECTION_SEPARATOR_END}'));
$detectionLogs = \substr($detectionLogs, 0, $separatorEnd);
$logs = \substr($logs, 0, $separator);
}
if ($resource->getCollection() === 'sites') {
$date = \date('H:i:s');
$logs .= "[$date] [appwrite] Screenshot capturing started. \n";
@@ -813,6 +837,31 @@ class Builds extends Action
$deployment->setAttribute('buildLogs', $logs);
if ($resource->getCollection() === 'sites' && !empty($detectionLogs)) {
$files = \explode("\n", $detectionLogs); // Parse output
$files = \array_filter($files); // Remove empty
$files = \array_map(fn ($file) => \trim($file), $files); // Remove whitepsaces
$files = \array_map(fn ($file) => \str_starts_with($file, './') ? \substr($file, 2) : $file, $files); // Remove beginning ./
$detector = new Rendering($files, $resource->getAttribute('framework', ''));
$detector
->addOption(new SSR())
->addOption(new XStatic());
$detection = $detector->detect();
$adapter = $resource->getAttribute('adapter', '');
if (empty($adapter)) {
$resource->setAttribute('adapter', $detection->getName());
$resource->setAttribute('fallbackFile', $detection->getFallbackFile() ?? '');
$resource = $dbForProject->updateDocument('sites', $resource->getId(), $resource);
$deployment->setAttribute('adapter', $detection->getName());
$deployment->setAttribute('fallbackFile', $detection->getFallbackFile() ?? '');
} elseif ($adapter === 'ssr' && $detection->getName() === 'static') {
throw new \Exception('Adapter mismatch. Detected: ' . $detection->getName() . ' does not match with the set adapter: ' . $adapter);
}
}
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
$queueForRealtime
@@ -1161,6 +1210,13 @@ class Builds extends Action
$message = "" . $message;
}
$separator = \strpos($message, '{APPWRITE_DETECTION_SEPARATOR_START}');
if ($separator !== false) {
$error = \substr($message, $separator + strlen('{APPWRITE_DETECTION_SEPARATOR_START}'));
$message = \substr($message, 0, $separator);
$message .= "\n" . $error;
}
$endTime = DateTime::now();
$durationEnd = \microtime(true);
$deployment->setAttribute('buildEndedAt', $endTime);
@@ -122,7 +122,7 @@ class Create extends Action
if ($status === 'created') {
$validators = [];
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
if (!$targetCNAME->isKnown() || $targetCNAME->isTest()) {
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
}
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
@@ -134,7 +134,7 @@ class Create extends Action
if ($status === 'created') {
$validators = [];
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
if (!$targetCNAME->isKnown() || $targetCNAME->isTest()) {
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
}
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
@@ -126,7 +126,7 @@ class Create extends Action
if ($status === 'created') {
$validators = [];
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
if (!$targetCNAME->isKnown() || $targetCNAME->isTest()) {
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
}
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
@@ -134,7 +134,7 @@ class Create extends Action
if ($status === 'created') {
$validators = [];
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
if (!$targetCNAME->isKnown() || $targetCNAME->isTest()) {
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
}
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
@@ -300,7 +300,7 @@ class Certificates extends Action
$validators = [];
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
if (!$targetCNAME->isKnown() || $targetCNAME->isTest()) {
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
}
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
@@ -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
*/
@@ -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']))
@@ -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']);
@@ -19,7 +29,28 @@ class V19 extends Filter
case 'functions.listExecutions':
unset($content['search']);
break;
case 'project.createVariable':
case 'project.listVariables':
case 'functions.createVariable':
case 'functions.updateVariable':
$content['secret'] = false;
break;
}
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;
}
}
@@ -16,14 +16,19 @@ class V19 extends Filter
Response::MODEL_FUNCTION => $this->parseFunction($content),
Response::MODEL_FUNCTION_LIST => $this->handleList($content, 'functions', fn ($item) => $this->parseFunction($item)),
Response::MODEL_DEPLOYMENT => $this->parseDeployment($content),
Response::MODEL_DEPLOYMENT_LIST => $this->handleList($content, 'deployments', fn ($item) => $this->parseDeployment($item)),
Response::MODEL_PROXY_RULE => $this->parseProxyRule($content),
Response::MODEL_PROXY_RULE_LIST => $this->handleList($content, 'rules', fn ($item) => $this->parseProxyRule($item)),
Response::MODEL_MIGRATION => $this->parseMigration($content),
Response::MODEL_MIGRATION_LIST => $this->handleList($content, 'migrations', fn ($item) => $this->parseMigration($item)),
Response::MODEL_PROJECT => $this->parseProject($content),
Response::MODEL_PROJECT_LIST => $this->handleList($content, 'projects', fn ($item) => $this->parseProject($item)),
Response::MODEL_PROVIDER_REPOSITORY => $this->parseProviderRepository($content),
Response::MODEL_TEMPLATE_VARIABLE => $this->parseTemplateVariable($content),
Response::MODEL_USAGE_FUNCTION => $this->parseUsageFunction($content),
Response::MODEL_USAGE_FUNCTIONS => $this->parseUsageFunctions($content),
Response::MODEL_VARIABLE => $this->parseVariable($content),
Response::MODEL_VARIABLE_LIST => $this->handleList($content, 'variables', fn ($item) => $this->parseVariable($item)),
default => $parsedResponse,
};
@@ -6,8 +6,8 @@ use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideServer;
use Utopia\App;
use Utopia\Database\Query;
use Utopia\System\System;
class ProxyCustomServerTest extends Scope
{
@@ -328,7 +328,7 @@ class ProxyCustomServerTest extends Scope
public function testUpdateRule(): void
{
// Create function appwrite-network domain
$domain = \uniqid() . '-cname-api.' . App::getEnv('_APP_DOMAIN_FUNCTIONS');
$domain = \uniqid() . '-cname-api.' . System::getEnv('_APP_DOMAIN_FUNCTIONS');
$rule = $this->createAPIRule($domain);
$this->assertEquals(201, $rule['headers']['status-code']);
@@ -337,7 +337,7 @@ class ProxyCustomServerTest extends Scope
$this->cleanupRule($rule['body']['$id']);
// Create site appwrite-network domain
$domain = \uniqid() . '-cname-api.' . App::getEnv('_APP_DOMAIN_SITES');
$domain = \uniqid() . '-cname-api.' . System::getEnv('_APP_DOMAIN_SITES');
$rule = $this->createAPIRule($domain);
$this->assertEquals(201, $rule['headers']['status-code']);
@@ -607,15 +607,6 @@ class RealtimeConsoleClientTest extends Scope
$this->assertContains("projects.{$projectId}", $response['data']['channels']);
$this->assertArrayHasKey('buildLogs', $response['data']['payload']);
// Ignore comparasion for first payload
if ($previousBuildLogs !== null) {
$this->assertNotEquals($previousBuildLogs, $response['data']['payload']['buildLogs']);
}
$previousBuildLogs = $response['data']['payload']['buildLogs'];
$this->assertEquals('building', $response['data']['payload']['status']);
if (!empty($response['data']['payload']['buildEndedAt'])) {
$this->assertNotEmpty($response['data']['payload']['buildEndedAt']);
$this->assertNotEmpty($response['data']['payload']['buildStartedAt']);
@@ -626,6 +617,15 @@ class RealtimeConsoleClientTest extends Scope
$this->assertNotEmpty($response['data']['payload']['buildLogs']);
break;
}
// Ignore comparasion for first payload
if ($previousBuildLogs !== null) {
$this->assertNotEquals($previousBuildLogs, $response['data']['payload']['buildLogs']);
}
$previousBuildLogs = $response['data']['payload']['buildLogs'];
$this->assertEquals('building', $response['data']['payload']['status']);
}
$response = json_decode($client->receive(), true);
@@ -2646,7 +2646,7 @@ class SitesCustomServerTest extends Scope
$this->assertEventually(function () use ($siteId, $deploymentId) {
$deployment = $this->getDeployment($siteId, $deploymentId);
$this->assertEquals('failed', $deployment['body']['status'], 'Deployment status does not match: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT));
$this->assertStringContainsString('Error:', $deployment['body']['buildLogs'], 'Deployment logs do not match: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT));
$this->assertStringContainsString('No such file or directory', $deployment['body']['buildLogs'], 'Deployment logs do not match: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT));
}, 100000, 500);
$this->cleanupSite($siteId);