Merge branch '1.9.x' into spike/isPublishable-migration

This commit is contained in:
premtsd-code
2026-05-07 12:42:04 +05:30
committed by GitHub
11 changed files with 214 additions and 75 deletions
+10
View File
@@ -623,6 +623,11 @@ return [
'description' => 'Synchronous function execution timed out. Use asynchronous execution instead, or ensure the execution duration doesn\'t exceed 30 seconds.',
'code' => 408,
],
Exception::FUNCTION_ASYNCHRONOUS_TIMEOUT => [
'name' => Exception::FUNCTION_ASYNCHRONOUS_TIMEOUT,
'description' => 'Asynchronous function execution timed out. Ensure the execution duration doesn\'t exceed the configured function timeout.',
'code' => 408,
],
Exception::FUNCTION_TEMPLATE_NOT_FOUND => [
'name' => Exception::FUNCTION_TEMPLATE_NOT_FOUND,
'description' => 'Function Template with the requested ID could not be found.',
@@ -687,6 +692,11 @@ return [
'description' => 'Build with the requested ID failed. Please check the logs for more information.',
'code' => 400,
],
Exception::BUILD_TIMEOUT => [
'name' => Exception::BUILD_TIMEOUT,
'description' => 'Build timed out. Increase the build timeout via the `_APP_COMPUTE_BUILD_TIMEOUT` environment variable, or simplify the build to complete within the limit.',
'code' => 408,
],
/** Deployments */
Exception::DEPLOYMENT_NOT_FOUND => [
+2 -2
View File
@@ -830,11 +830,11 @@ Http::patch('/v1/account/sessions/:sessionId')
$refreshToken = $session->getAttribute('providerRefreshToken', '');
$oAuthProviders = Config::getParam('oAuthProviders') ?? [];
$className = $oAuthProviders[$provider]['class'] ?? null;
if (!empty($provider) && ($className === null || !\class_exists($className))) {
if (!empty($refreshToken) && ($className === null || !\class_exists($className))) {
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
}
if (!empty($provider) && \class_exists($className)) {
if ($className !== null && \class_exists($className)) {
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
$appSecret = $project->getAttribute('oAuthProviders', [])[$provider . 'Secret'] ?? '{}';
+25 -20
View File
@@ -41,6 +41,7 @@ use Appwrite\Utopia\Response\Filters\V23 as ResponseV23;
use Appwrite\Utopia\Response\Filters\V24 as ResponseV24;
use Appwrite\Utopia\Response\Filters\V25 as ResponseV25;
use Appwrite\Utopia\View;
use Executor\Exception\Timeout as ExecutorTimeout;
use Executor\Executor;
use MaxMind\Db\Reader;
use Swoole\Http\Request as SwooleRequest;
@@ -581,26 +582,30 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
'site' => '',
};
$executionResponse = $executor->createExecution(
projectId: $project->getId(),
deploymentId: $deployment->getId(),
body: \strlen($body) > 0 ? $body : null,
variables: $vars,
timeout: $resource->getAttribute('timeout', 30),
image: $runtime['image'],
source: $source,
entrypoint: $entrypoint,
version: $version,
path: $path,
method: $method,
headers: $headers,
runtimeEntrypoint: $runtimeEntrypoint,
cpus: $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
logging: $resource->getAttribute('logging', true),
requestTimeout: 30,
responseFormat: Executor::RESPONSE_FORMAT_ARRAY_HEADERS
);
try {
$executionResponse = $executor->createExecution(
projectId: $project->getId(),
deploymentId: $deployment->getId(),
body: \strlen($body) > 0 ? $body : null,
variables: $vars,
timeout: $resource->getAttribute('timeout', 30),
image: $runtime['image'],
source: $source,
entrypoint: $entrypoint,
version: $version,
path: $path,
method: $method,
headers: $headers,
runtimeEntrypoint: $runtimeEntrypoint,
cpus: $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
logging: $resource->getAttribute('logging', true),
requestTimeout: 30,
responseFormat: Executor::RESPONSE_FORMAT_ARRAY_HEADERS
);
} catch (ExecutorTimeout $th) {
throw new AppwriteException(AppwriteException::FUNCTION_SYNCHRONOUS_TIMEOUT, previous: $th);
}
$headerOverrides = [];
+2
View File
@@ -178,6 +178,7 @@ class Exception extends \Exception
public const string FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported';
public const string FUNCTION_ENTRYPOINT_MISSING = 'function_entrypoint_missing';
public const string FUNCTION_SYNCHRONOUS_TIMEOUT = 'function_synchronous_timeout';
public const string FUNCTION_ASYNCHRONOUS_TIMEOUT = 'function_asynchronous_timeout';
public const string FUNCTION_TEMPLATE_NOT_FOUND = 'function_template_not_found';
public const string FUNCTION_RUNTIME_NOT_DETECTED = 'function_runtime_not_detected';
public const string FUNCTION_EXECUTE_PERMISSION_MISSING = 'function_execute_permission_missing';
@@ -192,6 +193,7 @@ class Exception extends \Exception
public const string BUILD_ALREADY_COMPLETED = 'build_already_completed';
public const string BUILD_CANCELED = 'build_canceled';
public const string BUILD_FAILED = 'build_failed';
public const string BUILD_TIMEOUT = 'build_timeout';
/** Execution */
public const string EXECUTION_NOT_FOUND = 'execution_not_found';
@@ -17,6 +17,7 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Usage\Context;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Response;
use Executor\Exception\Timeout as ExecutorTimeout;
use Executor\Executor;
use MaxMind\Db\Reader;
use Utopia\Auth\Proofs\Token;
@@ -417,25 +418,29 @@ class Create extends Base
$source = $deployment->getAttribute('buildPath', '');
$extension = str_ends_with($source, '.tar') ? 'tar' : 'tar.gz';
$command = $version === 'v2' ? '' : "cp /tmp/code.$extension /mnt/code/code.$extension && nohup helpers/start.sh \"$command\"";
$executionResponse = $executor->createExecution(
projectId: $project->getId(),
deploymentId: $deployment->getId(),
body: \strlen($body) > 0 ? $body : null,
variables: $vars,
timeout: $function->getAttribute('timeout', 0),
image: $runtime['image'],
source: $source,
entrypoint: $deployment->getAttribute('entrypoint', ''),
version: $version,
path: $path,
method: $method,
headers: $headers,
runtimeEntrypoint: $command,
cpus: $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
logging: $function->getAttribute('logging', true),
requestTimeout: 30
);
try {
$executionResponse = $executor->createExecution(
projectId: $project->getId(),
deploymentId: $deployment->getId(),
body: \strlen($body) > 0 ? $body : null,
variables: $vars,
timeout: $function->getAttribute('timeout', 0),
image: $runtime['image'],
source: $source,
entrypoint: $deployment->getAttribute('entrypoint', ''),
version: $version,
path: $path,
method: $method,
headers: $headers,
runtimeEntrypoint: $command,
cpus: $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
logging: $function->getAttribute('logging', true),
requestTimeout: 30
);
} catch (ExecutorTimeout $th) {
throw new AppwriteException(AppwriteException::FUNCTION_SYNCHRONOUS_TIMEOUT, previous: $th);
}
$headersFiltered = [];
foreach ($executionResponse['headers'] as $key => $value) {
@@ -10,11 +10,13 @@ use Appwrite\Event\Publisher\Screenshot;
use Appwrite\Event\Publisher\Usage as UsagePublisher;
use Appwrite\Event\Realtime;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Filter\BranchDomain as BranchDomainFilter;
use Appwrite\Usage\Context;
use Appwrite\Utopia\Response\Model\Deployment;
use Appwrite\Vcs\Comment;
use Exception;
use Executor\Exception\Timeout as ExecutorTimeout;
use Executor\Executor;
use Swoole\Coroutine as Co;
use Utopia\Cache\Cache;
@@ -34,6 +36,7 @@ use Utopia\Detector\Detector\Rendering;
use Utopia\Logger\Log;
use Utopia\Platform\Action;
use Utopia\Queue\Message;
use Utopia\Span\Span;
use Utopia\Storage\Device;
use Utopia\Storage\Device\Local;
use Utopia\System\System;
@@ -183,6 +186,12 @@ class Builds extends Action
array $platform,
int $timeout
): void {
Span::add('projectId', $project->getId());
Span::add('resourceId', $resource->getId());
Span::add('resourceType', $resource->getCollection());
Span::add('deploymentId', $deployment->getId());
Span::add('timeout', $timeout);
Console::info('Deployment action started');
$startTime = DateTime::now();
@@ -223,8 +232,12 @@ class Builds extends Action
$version = $this->getVersion($resource);
$runtime = $this->getRuntime($resource, $version);
Span::add('runtime', $resource->getAttribute($resource->getCollection() === 'sites' ? 'buildRuntime' : 'runtime', ''));
Span::add('version', $version);
$spec = Config::getParam('specifications')[$resource->getAttribute('buildSpecification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
Span::add('cpus', (float) ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT));
Span::add('memory', (int) ($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT));
// Realtime preparation
$event = "{$resource->getCollection()}.[{$resourceKey}].deployments.[deploymentId].update";
@@ -720,6 +733,9 @@ class Builds extends Action
);
Console::log('createRuntime finished');
} catch (ExecutorTimeout $error) {
Console::warning('createRuntime timed out');
$err = new AppwriteException(AppwriteException::BUILD_TIMEOUT, previous: $error);
} catch (\Throwable $error) {
Console::warning('createRuntime failed');
$err = $error;
@@ -1147,13 +1163,11 @@ class Builds extends Action
$message = \str_replace('{APPWRITE_DETECTION_SEPARATOR_START}', '', $message);
$message = \str_replace('{APPWRITE_DETECTION_SEPARATOR_END}', '', $message);
// Combine with previous logs if deployment got past build process
$previousLogs = '';
if (! is_null($deployment->getAttribute('buildSize', null))) {
$previousLogs = $deployment->getAttribute('buildLogs', '');
if (! empty($previousLogs)) {
$message = $previousLogs . "\n" . $message;
}
// Append error to whatever build logs were already streamed
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
$previousLogs = $deployment->getAttribute('buildLogs', '');
if (! empty($previousLogs)) {
$message = $previousLogs . "\n" . $message;
}
$endTime = DateTime::now();
+23 -18
View File
@@ -10,6 +10,7 @@ use Appwrite\Event\Realtime;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Utopia\Response\Model\Execution;
use Executor\Exception\Timeout as ExecutorTimeout;
use Executor\Executor;
use Utopia\Bus\Bus;
use Utopia\Config\Config;
@@ -565,24 +566,28 @@ class Functions extends Action
Span::add('trigger', $trigger);
Span::current()?->finish();
}
$executionResponse = $executor->createExecution(
projectId: $project->getId(),
deploymentId: $deploymentId,
body: \strlen($body) > 0 ? $body : null,
variables: $vars,
timeout: $function->getAttribute('timeout', 0),
image: $runtime['image'],
source: $source,
entrypoint: $deployment->getAttribute('entrypoint', ''),
version: $version,
path: $path,
method: $method,
headers: $headers,
runtimeEntrypoint: $command,
cpus: $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
logging: $function->getAttribute('logging', true),
);
try {
$executionResponse = $executor->createExecution(
projectId: $project->getId(),
deploymentId: $deploymentId,
body: \strlen($body) > 0 ? $body : null,
variables: $vars,
timeout: $function->getAttribute('timeout', 0),
image: $runtime['image'],
source: $source,
entrypoint: $deployment->getAttribute('entrypoint', ''),
version: $version,
path: $path,
method: $method,
headers: $headers,
runtimeEntrypoint: $command,
cpus: $spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT,
memory: $spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT,
logging: $function->getAttribute('logging', true),
);
} catch (ExecutorTimeout $th) {
throw new AppwriteException(AppwriteException::FUNCTION_ASYNCHRONOUS_TIMEOUT, previous: $th);
}
$status = $executionResponse['statusCode'] >= 500 ? 'failed' : 'completed';
+7
View File
@@ -0,0 +1,7 @@
<?php
namespace Executor;
class Exception extends \Exception
{
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace Executor\Exception;
use Executor\Exception;
use Throwable;
class Timeout extends Exception
{
public function __construct(
string $message,
private readonly int $timeoutSeconds,
int $code = 0,
?Throwable $previous = null,
) {
parent::__construct($message, $code, $previous);
}
public function getTimeoutSeconds(): int
{
return $this->timeoutSeconds;
}
}
+9 -9
View File
@@ -2,9 +2,9 @@
namespace Executor;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Utopia\Fetch\BodyMultipart;
use Exception;
use Executor\Exception as ExecutorException;
use Executor\Exception\Timeout as ExecutorTimeout;
use Utopia\System\System;
class Executor
@@ -104,7 +104,7 @@ class Executor
$status = $response['headers']['status-code'];
if ($status >= 400) {
$message = \is_string($response['body']) ? $response['body'] : $response['body']['message'];
throw new \Exception($message, $status);
throw new ExecutorException($message, $status);
}
return $response['body'];
@@ -163,7 +163,7 @@ class Executor
}
if ($status >= 400) {
throw new \Exception($message, $status);
throw new ExecutorException($message, $status);
}
return $response['body'];
@@ -247,7 +247,7 @@ class Executor
$status = $response['headers']['status-code'];
if ($status >= 400) {
$message = \is_string($response['body']) ? $response['body'] : $response['body']['message'];
throw new \Exception($message, $status);
throw new ExecutorException($message, $status);
}
$headers = $response['body']['headers'] ?? [];
@@ -281,7 +281,7 @@ class Executor
$status = $response['headers']['status-code'];
if ($status >= 400) {
$message = \is_string($response['body']) ? $response['body'] : $response['body']['message'];
throw new \Exception($message, $status);
throw new ExecutorException($message, $status);
}
return $response['body'];
@@ -401,7 +401,7 @@ class Executor
$json = json_decode($responseBody, true);
if ($json === null) {
throw new Exception('Failed to parse response: ' . $responseBody);
throw new ExecutorException('Failed to parse response: ' . $responseBody);
}
$responseBody = $json;
@@ -412,9 +412,9 @@ class Executor
if ($curlError) {
if ($curlError == CURLE_OPERATION_TIMEDOUT) {
throw new AppwriteException(AppwriteException::FUNCTION_SYNCHRONOUS_TIMEOUT);
throw new ExecutorTimeout('Executor request timed out', $timeout);
}
throw new Exception($curlErrorMessage . ' with status code ' . $responseStatus, $responseStatus);
throw new ExecutorException($curlErrorMessage . ' with status code ' . $responseStatus, $responseStatus);
}
$responseHeaders['status-code'] = $responseStatus;
@@ -4163,4 +4163,72 @@ class AccountCustomClientTest extends Scope
$this->assertEquals(401, $verification3['headers']['status-code']);
}
public function testRefreshEmailPasswordSession(): void
{
$email = uniqid() . 'user@localhost.test';
$account = $this->client->call(Client::METHOD_POST, '/account', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'userId' => ID::unique(),
'email' => $email,
'password' => 'password',
]);
$this->assertEquals(201, $account['headers']['status-code']);
$session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
]), [
'email' => $email,
'password' => 'password',
]);
$this->assertEquals(201, $session['headers']['status-code']);
$this->assertNotEmpty($session['body']['$id']);
$sessionId = $session['body']['$id'];
$cookie = 'a_session_' . $this->getProject()['$id'] . '=' .$session['cookies']['a_session_' . $this->getProject()['$id']];
$session = $this->client->call(Client::METHOD_GET, '/account/sessions/current', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => $cookie,
]));
$this->assertEquals(200, $session['headers']['status-code']);
$this->assertNotEmpty($session['body']['expire']);
$expiryBefore = $session['body']['expire'];
\sleep(3); // Small delay to ensure expiry an expand
$session = $this->client->call(Client::METHOD_PATCH, '/account/sessions/' . $sessionId, array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => $cookie,
]));
$this->assertEquals(200, $session['headers']['status-code']);
$this->assertNotEmpty($session['body']['expire']);
$expiryAfter = $session['body']['expire'];
$this->assertGreaterThan(\strtotime($expiryBefore), \strtotime($expiryAfter));
$session = $this->client->call(Client::METHOD_GET, '/account/sessions/current', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'cookie' => $cookie,
]));
$this->assertEquals(200, $session['headers']['status-code']);
$this->assertEquals(\strtotime($expiryAfter), \strtotime($session['body']['expire']));
}
}