mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch 'feat-1.8.x-new-schema' into feat-disable-screenshots
This commit is contained in:
@@ -567,6 +567,17 @@ return [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('deploymentRetention'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('deploymentInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -766,8 +777,31 @@ return [
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
// At the moment, always empty (no runtime supports it yet)
|
||||
'array' => false,
|
||||
'$id' => ID::custom('specification'),
|
||||
'$id' => ID::custom('startCommand'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('buildSpecification'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('runtimeSpecification'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
@@ -1035,6 +1069,17 @@ return [
|
||||
'default' => null,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('startCommand'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('fallbackFile'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -1046,6 +1091,17 @@ return [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('deploymentRetention'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => 0,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('deploymentInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -1191,7 +1247,18 @@ return [
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('specification'),
|
||||
'$id' => ID::custom('buildSpecification'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('runtimeSpecification'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 128,
|
||||
@@ -1368,6 +1435,17 @@ return [
|
||||
'default' => null,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('startCommand'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('buildOutput'),
|
||||
|
||||
@@ -306,6 +306,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
'resourceType' => $resourceCollection,
|
||||
'entrypoint' => $resource->getAttribute('entrypoint', ''),
|
||||
'buildCommands' => \implode(' && ', $commands),
|
||||
'startCommand' => $resource->getAttribute('startCommand', ''),
|
||||
'buildOutput' => $resource->getAttribute('outputDirectory', ''),
|
||||
'adapter' => $resource->getAttribute('adapter', ''),
|
||||
'fallbackFile' => $resource->getAttribute('fallbackFile', ''),
|
||||
|
||||
@@ -29,6 +29,8 @@ use Appwrite\Utopia\Response\Filters\V16 as ResponseV16;
|
||||
use Appwrite\Utopia\Response\Filters\V17 as ResponseV17;
|
||||
use Appwrite\Utopia\Response\Filters\V18 as ResponseV18;
|
||||
use Appwrite\Utopia\Response\Filters\V19 as ResponseV19;
|
||||
use Appwrite\Utopia\Response\Filters\V20 as ResponseV20;
|
||||
use Appwrite\Utopia\Response\Filters\V21 as ResponseV21;
|
||||
use Appwrite\Utopia\View;
|
||||
use Executor\Executor;
|
||||
use MaxMind\Db\Reader;
|
||||
@@ -307,7 +309,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
};
|
||||
|
||||
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
|
||||
$spec = Config::getParam('specifications')[$resource->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
$spec = Config::getParam('specifications')[$resource->getAttribute('runtimeSpecification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
|
||||
$runtime = match ($type) {
|
||||
'function' => $runtimes[$resource->getAttribute('runtime')] ?? null,
|
||||
@@ -537,6 +539,10 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($deployment->getAttribute('startCommand', ''))) {
|
||||
$startCommand = 'cd /usr/local/server/src/function/ && ' . $deployment->getAttribute('startCommand', '');
|
||||
}
|
||||
|
||||
$runtimeEntrypoint = match ($version) {
|
||||
'v2' => '',
|
||||
default => "cp /tmp/code.$extension /mnt/code/code.$extension && nohup helpers/start.sh \"$startCommand\"",
|
||||
@@ -948,17 +954,23 @@ App::init()
|
||||
*/
|
||||
$responseFormat = $request->getHeader('x-appwrite-response-format', System::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
|
||||
if ($responseFormat) {
|
||||
if (version_compare($responseFormat, '1.4.0', '<')) {
|
||||
$response->addFilter(new ResponseV16());
|
||||
if (version_compare($responseFormat, '1.9.0', '<')) {
|
||||
$response->addFilter(new ResponseV21());
|
||||
}
|
||||
if (version_compare($responseFormat, '1.5.0', '<')) {
|
||||
$response->addFilter(new ResponseV17());
|
||||
if (version_compare($responseFormat, '1.8.0', '<')) {
|
||||
$response->addFilter(new ResponseV20());
|
||||
}
|
||||
if (version_compare($responseFormat, '1.7.0', '<')) {
|
||||
$response->addFilter(new ResponseV19());
|
||||
}
|
||||
if (version_compare($responseFormat, '1.6.0', '<')) {
|
||||
$response->addFilter(new ResponseV18());
|
||||
}
|
||||
if (version_compare($responseFormat, '1.7.0', '<')) {
|
||||
$response->addFilter(new ResponseV19());
|
||||
if (version_compare($responseFormat, '1.5.0', '<')) {
|
||||
$response->addFilter(new ResponseV17());
|
||||
}
|
||||
if (version_compare($responseFormat, '1.4.0', '<')) {
|
||||
$response->addFilter(new ResponseV16());
|
||||
}
|
||||
if (version_compare($responseFormat, APP_VERSION_STABLE, '>')) {
|
||||
$warnings[] = "The current SDK is built for Appwrite " . $responseFormat . ". However, the current Appwrite server version is " . APP_VERSION_STABLE . ". Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks";
|
||||
|
||||
@@ -12,6 +12,7 @@ use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\System\System;
|
||||
@@ -219,6 +220,48 @@ App::post('/v1/mock/api-key-unprefixed')
|
||||
->dynamic($key, Response::MODEL_KEY);
|
||||
});
|
||||
|
||||
App::post('/v1/mock/time-travels')
|
||||
->desc('Create a time-travel to change $createdAt')
|
||||
->groups(['mock', 'api'])
|
||||
->label('scope', 'public')
|
||||
->label('docs', false)
|
||||
->param('projectId', '', new UID(), 'Project ID.')
|
||||
->param('resourceType', '', new WhiteList(['deployment']), 'Type of resource.')
|
||||
->param('resourceId', '', new UID(), 'ID of resource.')
|
||||
->param('createdAt', '', new DatetimeValidator(), 'New value for $createdAt')
|
||||
->inject('response')
|
||||
->inject('getProjectDB')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $resourceType, string $resourceId, string $createdAt, Response $response, callable $getProjectDB, Database $dbForPlatform) {
|
||||
$isDevelopment = System::getEnv('_APP_ENV', 'development') === 'development';
|
||||
|
||||
if (!$isDevelopment) {
|
||||
throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = match($resourceType) {
|
||||
'deployment' => 'deployments',
|
||||
default => throw new Exception(Exception::GENERAL_NOT_IMPLEMENTED)
|
||||
};
|
||||
|
||||
/** @var Database $dbForProject */
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
$update = new Document([
|
||||
'$createdAt' => $createdAt,
|
||||
]);
|
||||
|
||||
$dbForProject->withPreserveDates(fn () => $dbForProject->updateDocument($collection, $resourceId, $update));
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
App::get('/v1/mock/github/callback')
|
||||
->desc('Create installation document using GitHub installation id')
|
||||
->groups(['mock', 'api', 'vcs'])
|
||||
|
||||
@@ -90,6 +90,7 @@ const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=
|
||||
const APP_COMPUTE_CPUS_DEFAULT = 0.5;
|
||||
const APP_COMPUTE_MEMORY_DEFAULT = 512;
|
||||
const APP_COMPUTE_SPECIFICATION_DEFAULT = Specification::S_1VCPU_512MB;
|
||||
const APP_COMPUTE_DEPLOYMENT_MAX_RETENTION = 100 * 365; // 100 years
|
||||
const APP_SDK_PLATFORM_SERVER = 'server';
|
||||
const APP_SDK_PLATFORM_CLIENT = 'client';
|
||||
const APP_SDK_PLATFORM_CONSOLE = 'console';
|
||||
|
||||
@@ -106,6 +106,7 @@ class Base extends Action
|
||||
'resourceType' => 'functions',
|
||||
'entrypoint' => $entrypoint,
|
||||
'buildCommands' => $function->getAttribute('commands', ''),
|
||||
'startCommand' => $function->getAttribute('startCommand', ''),
|
||||
'type' => 'vcs',
|
||||
'installationId' => $installation->getId(),
|
||||
'installationInternalId' => $installation->getSequence(),
|
||||
@@ -202,6 +203,7 @@ class Base extends Action
|
||||
'resourceInternalId' => $site->getSequence(),
|
||||
'resourceType' => 'sites',
|
||||
'buildCommands' => implode(' && ', $commands),
|
||||
'startCommand' => $site->getAttribute('startCommand', ''),
|
||||
'buildOutput' => $site->getAttribute('outputDirectory', ''),
|
||||
'adapter' => $site->getAttribute('adapter', ''),
|
||||
'fallbackFile' => $site->getAttribute('fallbackFile', ''),
|
||||
|
||||
@@ -243,6 +243,7 @@ class Create extends Action
|
||||
'resourceType' => 'functions',
|
||||
'entrypoint' => $entrypoint,
|
||||
'buildCommands' => $commands,
|
||||
'startCommand' => $function->getAttribute('startCommand', ''),
|
||||
'sourcePath' => $path,
|
||||
'sourceSize' => $fileSize,
|
||||
'totalSize' => $fileSize,
|
||||
@@ -280,6 +281,7 @@ class Create extends Action
|
||||
'resourceType' => 'functions',
|
||||
'entrypoint' => $entrypoint,
|
||||
'buildCommands' => $commands,
|
||||
'startCommand' => $function->getAttribute('startCommand', ''),
|
||||
'sourcePath' => $path,
|
||||
'sourceSize' => $fileSize,
|
||||
'totalSize' => $fileSize,
|
||||
|
||||
@@ -104,6 +104,7 @@ class Create extends Action
|
||||
'totalSize' => $deployment->getAttribute('sourceSize', 0),
|
||||
'entrypoint' => $function->getAttribute('entrypoint'),
|
||||
'buildCommands' => $function->getAttribute('commands', ''),
|
||||
'startCommand' => $function->getAttribute('startCommand', ''),
|
||||
'buildStartedAt' => null,
|
||||
'buildEndedAt' => null,
|
||||
'buildDuration' => null,
|
||||
|
||||
@@ -154,6 +154,7 @@ class Create extends Base
|
||||
'resourceType' => 'functions',
|
||||
'entrypoint' => $function->getAttribute('entrypoint', ''),
|
||||
'buildCommands' => $function->getAttribute('commands', ''),
|
||||
'startCommand' => $function->getAttribute('startCommand', ''),
|
||||
'providerRepositoryName' => $repository,
|
||||
'providerRepositoryOwner' => $owner,
|
||||
'providerRepositoryUrl' => $repositoryUrl,
|
||||
|
||||
@@ -172,7 +172,7 @@ class Create extends Base
|
||||
|
||||
$version = $function->getAttribute('version', 'v2');
|
||||
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
|
||||
$spec = Config::getParam('specifications')[$function->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
$spec = Config::getParam('specifications')[$function->getAttribute('runtimeSpecification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
|
||||
$runtime = (isset($runtimes[$function->getAttribute('runtime', '')])) ? $runtimes[$function->getAttribute('runtime', '')] : null;
|
||||
|
||||
@@ -403,6 +403,11 @@ class Create extends Base
|
||||
try {
|
||||
$version = $function->getAttribute('version', 'v2');
|
||||
$command = $runtime['startCommand'];
|
||||
|
||||
if (!empty($deployment->getAttribute('startCommand', ''))) {
|
||||
$command = 'cd /usr/local/server/src/function/ && ' . $deployment->getAttribute('startCommand', '');
|
||||
}
|
||||
|
||||
$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\"";
|
||||
|
||||
@@ -93,16 +93,23 @@ class Create extends Base
|
||||
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function.', true)
|
||||
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true)
|
||||
->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true)
|
||||
->param('specification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
->param('buildSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Runtime specification for the function and builds.', true, ['plan'])
|
||||
), 'Build specification for the function deployments.', true, ['plan'])
|
||||
->param('runtimeSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Runtime specification for the function executions.', 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)
|
||||
->param('deploymentRetention', 0, new Range(0, APP_COMPUTE_DEPLOYMENT_MAX_RETENTION), 'Days to keep non-active deployments before deletion. Value 0 means all deployments will be kept.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('timelimit')
|
||||
@@ -136,11 +143,13 @@ class Create extends Base
|
||||
string $providerBranch,
|
||||
bool $providerSilentMode,
|
||||
string $providerRootDirectory,
|
||||
string $specification,
|
||||
string $buildSpecification,
|
||||
string $runtimeSpecification,
|
||||
string $templateRepository,
|
||||
string $templateOwner,
|
||||
string $templateRootDirectory,
|
||||
string $templateVersion,
|
||||
int $deploymentRetention,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
callable $timelimit,
|
||||
@@ -211,6 +220,7 @@ class Create extends Base
|
||||
'logging' => $logging,
|
||||
'name' => $name,
|
||||
'runtime' => $runtime,
|
||||
'deploymentRetention' => $deploymentRetention,
|
||||
'deploymentInternalId' => '',
|
||||
'deploymentId' => '',
|
||||
'events' => $events,
|
||||
@@ -231,7 +241,8 @@ class Create extends Base
|
||||
'providerBranch' => $providerBranch,
|
||||
'providerRootDirectory' => $providerRootDirectory,
|
||||
'providerSilentMode' => $providerSilentMode,
|
||||
'specification' => $specification
|
||||
'buildSpecification' => $buildSpecification,
|
||||
'runtimeSpecification' => $runtimeSpecification,
|
||||
]));
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::FUNCTION_ALREADY_EXISTS);
|
||||
@@ -340,6 +351,7 @@ class Create extends Base
|
||||
'resourceType' => 'functions',
|
||||
'entrypoint' => $function->getAttribute('entrypoint', ''),
|
||||
'buildCommands' => $function->getAttribute('commands', ''),
|
||||
'startCommand' => $function->getAttribute('startCommand', ''),
|
||||
'type' => 'manual',
|
||||
'activate' => true,
|
||||
]));
|
||||
|
||||
@@ -89,12 +89,19 @@ class Update extends Base
|
||||
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function', true)
|
||||
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true)
|
||||
->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true)
|
||||
->param('specification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
->param('buildSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Runtime specification for the function and builds.', true, ['plan'])
|
||||
), 'Build specification for the function deployments.', true, ['plan'])
|
||||
->param('runtimeSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Runtime specification for the function executions.', true, ['plan'])
|
||||
->param('deploymentRetention', 0, new Range(0, APP_COMPUTE_DEPLOYMENT_MAX_RETENTION), 'Days to keep non-active deployments before deletion. Value 0 means all deployments will be kept.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -125,7 +132,9 @@ class Update extends Base
|
||||
string $providerBranch,
|
||||
bool $providerSilentMode,
|
||||
string $providerRootDirectory,
|
||||
string $specification,
|
||||
string $buildSpecification,
|
||||
string $runtimeSpecification,
|
||||
int $deploymentRetention,
|
||||
Request $request,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
@@ -215,7 +224,7 @@ class Update extends Base
|
||||
'resourceId' => $function->getId(),
|
||||
'resourceInternalId' => $function->getSequence(),
|
||||
'resourceType' => 'function',
|
||||
'providerPullRequestIds' => []
|
||||
'providerPullRequestIds' => [],
|
||||
]));
|
||||
|
||||
$repositoryId = $repository->getId();
|
||||
@@ -235,13 +244,22 @@ class Update extends Base
|
||||
}
|
||||
|
||||
// Enforce Cold Start if spec limits change.
|
||||
if ($function->getAttribute('specification') !== $specification && !empty($function->getAttribute('deploymentId'))) {
|
||||
try {
|
||||
$executor->deleteRuntime($project->getId(), $function->getAttribute('deploymentId'));
|
||||
} catch (\Throwable $th) {
|
||||
// Don't throw if the deployment doesn't exist
|
||||
if ($th->getCode() !== 404) {
|
||||
throw $th;
|
||||
if (!empty($function->getAttribute('deploymentId'))) {
|
||||
$specsChanged = false;
|
||||
if ($function->getAttribute('runtimeSpecification') !== $runtimeSpecification) {
|
||||
$specsChanged = true;
|
||||
} elseif ($function->getAttribute('buildSpecification') !== $buildSpecification) {
|
||||
$specsChanged = true;
|
||||
}
|
||||
|
||||
if ($specsChanged) {
|
||||
try {
|
||||
$executor->deleteRuntime($project->getId(), $function->getAttribute('deploymentId'));
|
||||
} catch (\Throwable $th) {
|
||||
// Don't throw if the deployment doesn't exist
|
||||
if ($th->getCode() !== 404) {
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,6 +277,7 @@ class Update extends Base
|
||||
'entrypoint' => $entrypoint,
|
||||
'commands' => $commands,
|
||||
'scopes' => $scopes,
|
||||
'deploymentRetention' => $deploymentRetention,
|
||||
'installationId' => $installation->getId(),
|
||||
'installationInternalId' => $installation->getSequence(),
|
||||
'providerRepositoryId' => $providerRepositoryId,
|
||||
@@ -267,7 +286,8 @@ class Update extends Base
|
||||
'providerBranch' => $providerBranch,
|
||||
'providerRootDirectory' => $providerRootDirectory,
|
||||
'providerSilentMode' => $providerSilentMode,
|
||||
'specification' => $specification,
|
||||
'buildSpecification' => $buildSpecification,
|
||||
'runtimeSpecification' => $runtimeSpecification,
|
||||
'search' => implode(' ', [$functionId, $name, $runtime]),
|
||||
])));
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ class Builds extends Action
|
||||
$version = $this->getVersion($resource);
|
||||
$runtime = $this->getRuntime($resource, $version);
|
||||
|
||||
$spec = Config::getParam('specifications')[$resource->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
$spec = Config::getParam('specifications')[$resource->getAttribute('buildSpecification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
|
||||
if ($resource->getCollection() === 'functions' && \is_null($runtime)) {
|
||||
throw new \Exception('Runtime "' . $resource->getAttribute('runtime', '') . '" is not supported');
|
||||
@@ -1358,7 +1358,7 @@ class Builds extends Action
|
||||
|
||||
protected function sendUsage(Document $resource, Document $deployment, Document $project, StatsUsage $queue): void
|
||||
{
|
||||
$spec = Config::getParam('specifications')[$resource->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
$spec = Config::getParam('specifications')[$resource->getAttribute('buildSpecification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
|
||||
switch ($deployment->getAttribute('status')) {
|
||||
case 'ready':
|
||||
|
||||
@@ -251,6 +251,7 @@ class Create extends Action
|
||||
'resourceId' => $site->getId(),
|
||||
'resourceType' => 'sites',
|
||||
'buildCommands' => \implode(' && ', $commands),
|
||||
'startCommand' => $site->getAttribute('startCommand', ''),
|
||||
'buildOutput' => $outputDirectory,
|
||||
'adapter' => $site->getAttribute('adapter', ''),
|
||||
'fallbackFile' => $site->getAttribute('fallbackFile', ''),
|
||||
@@ -318,6 +319,7 @@ class Create extends Action
|
||||
'resourceId' => $site->getId(),
|
||||
'resourceType' => 'sites',
|
||||
'buildCommands' => \implode(' && ', $commands),
|
||||
'startCommand' => $site->getAttribute('startCommand', ''),
|
||||
'buildOutput' => $outputDirectory,
|
||||
'adapter' => $site->getAttribute('adapter', ''),
|
||||
'fallbackFile' => $site->getAttribute('fallbackFile', ''),
|
||||
|
||||
@@ -117,6 +117,7 @@ class Create extends Action
|
||||
'sourcePath' => $destination,
|
||||
'totalSize' => $deployment->getAttribute('sourceSize', 0),
|
||||
'buildCommands' => \implode(' && ', $commands),
|
||||
'startCommand' => $site->getAttribute('startCommand', ''),
|
||||
'buildOutput' => $site->getAttribute('outputDirectory', ''),
|
||||
'adapter' => $site->getAttribute('adapter', ''),
|
||||
'fallbackFile' => $site->getAttribute('fallbackFile', ''),
|
||||
|
||||
@@ -163,6 +163,7 @@ class Create extends Base
|
||||
'resourceInternalId' => $site->getSequence(),
|
||||
'resourceType' => 'sites',
|
||||
'buildCommands' => \implode(' && ', $commands),
|
||||
'startCommand' => $site->getAttribute('startCommand', ''),
|
||||
'buildOutput' => $site->getAttribute('outputDirectory', ''),
|
||||
'providerRepositoryName' => $repository,
|
||||
'providerRepositoryOwner' => $owner,
|
||||
|
||||
@@ -69,6 +69,7 @@ class Create extends Base
|
||||
->param('timeout', 30, new Range(1, (int) System::getEnv('_APP_SITES_TIMEOUT', 30)), 'Maximum request time in seconds.', true)
|
||||
->param('installCommand', '', new Text(8192, 0), 'Install Command.', true)
|
||||
->param('buildCommand', '', new Text(8192, 0), 'Build Command.', true)
|
||||
->param('startCommand', '', new Text(8192, 0), 'Custom start command. Leave empty to use default.', true)
|
||||
->param('outputDirectory', '', new Text(8192, 0), 'Output Directory for site.', true)
|
||||
->param('buildRuntime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Runtime to use during build step.')
|
||||
->param('adapter', '', new WhiteList(['static', 'ssr']), 'Framework adapter defining rendering strategy. Allowed values are: static, ssr', true)
|
||||
@@ -79,12 +80,19 @@ class Create extends Base
|
||||
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the site? In silent mode, comments will not be made on commits and pull requests.', true)
|
||||
->param('providerRootDirectory', '', new Text(128, 0), 'Path to site code in the linked repo.', true)
|
||||
->param('deploymentScreenshots', true, new Boolean(), 'Whether to generate screenshots during deployment.', true)
|
||||
->param('specification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
->param('buildSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Framework specification for the site and builds.', true, ['plan'])
|
||||
), 'Build specification for the site deployments.', true, ['plan'])
|
||||
->param('runtimeSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Runtime specification for the function SSR executions.', true, ['plan'])
|
||||
->param('deploymentRetention', 0, new Range(0, APP_COMPUTE_DEPLOYMENT_MAX_RETENTION), 'Days to keep non-active deployments before deletion. Value 0 means all deployments will be kept.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
@@ -102,6 +110,7 @@ class Create extends Base
|
||||
int $timeout,
|
||||
string $installCommand,
|
||||
string $buildCommand,
|
||||
string $startCommand,
|
||||
string $outputDirectory,
|
||||
string $buildRuntime,
|
||||
string $adapter,
|
||||
@@ -112,7 +121,9 @@ class Create extends Base
|
||||
bool $providerSilentMode,
|
||||
string $providerRootDirectory,
|
||||
bool $deploymentScreenshots,
|
||||
string $specification,
|
||||
string $buildSpecification,
|
||||
string $runtimeSpecification,
|
||||
int $deploymentRetention,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
Document $project,
|
||||
@@ -147,11 +158,13 @@ class Create extends Base
|
||||
'logging' => $logging,
|
||||
'name' => $name,
|
||||
'framework' => $framework,
|
||||
'deploymentRetention' => $deploymentRetention,
|
||||
'deploymentInternalId' => '',
|
||||
'deploymentId' => '',
|
||||
'timeout' => $timeout,
|
||||
'installCommand' => $installCommand,
|
||||
'buildCommand' => $buildCommand,
|
||||
'startCommand' => $startCommand,
|
||||
'outputDirectory' => $outputDirectory,
|
||||
'search' => implode(' ', [$siteId, $name, $framework]),
|
||||
'fallbackFile' => $fallbackFile,
|
||||
@@ -163,7 +176,8 @@ class Create extends Base
|
||||
'providerBranch' => $providerBranch,
|
||||
'providerRootDirectory' => $providerRootDirectory,
|
||||
'providerSilentMode' => $providerSilentMode,
|
||||
'specification' => $specification,
|
||||
'buildSpecification' => $buildSpecification,
|
||||
'runtimeSpecification' => $runtimeSpecification,
|
||||
'buildRuntime' => $buildRuntime,
|
||||
'adapter' => $adapter,
|
||||
'deploymentScreenshots' => $deploymentScreenshots,
|
||||
|
||||
@@ -73,6 +73,7 @@ class Update extends Base
|
||||
->param('timeout', 30, new Range(1, (int) System::getEnv('_APP_SITES_TIMEOUT', 30)), 'Maximum request time in seconds.', true)
|
||||
->param('installCommand', '', new Text(8192, 0), 'Install Command.', true)
|
||||
->param('buildCommand', '', new Text(8192, 0), 'Build Command.', true)
|
||||
->param('startCommand', '', new Text(8192, 0), 'Custom start command. Leave empty to use default.', true)
|
||||
->param('outputDirectory', '', new Text(8192, 0), 'Output Directory for site.', true)
|
||||
->param('buildRuntime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Runtime to use during build step.', true)
|
||||
->param('adapter', '', new WhiteList(['static', 'ssr']), 'Framework adapter defining rendering strategy. Allowed values are: static, ssr', true)
|
||||
@@ -83,12 +84,19 @@ class Update extends Base
|
||||
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the site? In silent mode, comments will not be made on commits and pull requests.', true)
|
||||
->param('providerRootDirectory', '', new Text(128, 0), 'Path to site code in the linked repo.', true)
|
||||
->param('deploymentScreenshots', true, new Boolean(), 'Whether to generate screenshots during deployment.', true)
|
||||
->param('specification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
->param('buildSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Framework specification for the site and builds.', true, ['plan'])
|
||||
), 'Build specification for the site deployments.', true, ['plan'])
|
||||
->param('runtimeSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('specifications', []),
|
||||
System::getEnv('_APP_COMPUTE_CPUS', 0),
|
||||
System::getEnv('_APP_COMPUTE_MEMORY', 0)
|
||||
), 'Runtime specification for the function SSR executions.', true, ['plan'])
|
||||
->param('deploymentRetention', 0, new Range(0, APP_COMPUTE_DEPLOYMENT_MAX_RETENTION), 'Days to keep non-active deployments before deletion. Value 0 means all deployments will be kept.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -110,6 +118,7 @@ class Update extends Base
|
||||
int $timeout,
|
||||
string $installCommand,
|
||||
string $buildCommand,
|
||||
string $startCommand,
|
||||
string $outputDirectory,
|
||||
string $buildRuntime,
|
||||
string $adapter,
|
||||
@@ -120,7 +129,9 @@ class Update extends Base
|
||||
bool $providerSilentMode,
|
||||
string $providerRootDirectory,
|
||||
bool $deploymentScreenshots,
|
||||
string $specification,
|
||||
string $buildSpecification,
|
||||
string $runtimeSpecification,
|
||||
int $deploymentRetention,
|
||||
Request $request,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
@@ -228,6 +239,7 @@ class Update extends Base
|
||||
$site->getAttribute('name') !== $name ||
|
||||
$site->getAttribute('buildCommand') !== $buildCommand ||
|
||||
$site->getAttribute('installCommand') !== $installCommand ||
|
||||
$site->getAttribute('startCommand') !== $startCommand ||
|
||||
$site->getAttribute('outputDirectory') !== $outputDirectory ||
|
||||
$site->getAttribute('providerRootDirectory') !== $providerRootDirectory ||
|
||||
$site->getAttribute('framework') !== $framework
|
||||
@@ -235,14 +247,22 @@ class Update extends Base
|
||||
$live = false;
|
||||
}
|
||||
|
||||
// Enforce Cold Start if spec limits change.
|
||||
if ($site->getAttribute('specification') !== $specification && !empty($site->getAttribute('deploymentId'))) {
|
||||
try {
|
||||
$executor->deleteRuntime($project->getId(), $site->getAttribute('deploymentId'));
|
||||
} catch (\Throwable $th) {
|
||||
// Don't throw if the deployment doesn't exist
|
||||
if ($th->getCode() !== 404) {
|
||||
throw $th;
|
||||
if (!empty($site->getAttribute('deploymentId'))) {
|
||||
$specsChanged = false;
|
||||
if ($site->getAttribute('runtimeSpecification') !== $runtimeSpecification) {
|
||||
$specsChanged = true;
|
||||
} elseif ($site->getAttribute('buildSpecification') !== $buildSpecification) {
|
||||
$specsChanged = true;
|
||||
}
|
||||
|
||||
if ($specsChanged) {
|
||||
try {
|
||||
$executor->deleteRuntime($project->getId(), $site->getAttribute('deploymentId'));
|
||||
} catch (\Throwable $th) {
|
||||
// Don't throw if the deployment doesn't exist
|
||||
if ($th->getCode() !== 404) {
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,8 +274,10 @@ class Update extends Base
|
||||
'logging' => $logging,
|
||||
'live' => $live,
|
||||
'timeout' => $timeout,
|
||||
'deploymentRetention' => $deploymentRetention,
|
||||
'installCommand' => $installCommand,
|
||||
'buildCommand' => $buildCommand,
|
||||
'startCommand' => $startCommand,
|
||||
'outputDirectory' => $outputDirectory,
|
||||
'installationId' => $installation->getId(),
|
||||
'installationInternalId' => $installation->getSequence(),
|
||||
@@ -265,7 +287,8 @@ class Update extends Base
|
||||
'providerBranch' => $providerBranch,
|
||||
'providerRootDirectory' => $providerRootDirectory,
|
||||
'providerSilentMode' => $providerSilentMode,
|
||||
'specification' => $specification,
|
||||
'buildSpecification' => $buildSpecification,
|
||||
'runtimeSpecification' => $runtimeSpecification,
|
||||
'search' => implode(' ', [$siteId, $name, $framework]),
|
||||
'buildRuntime' => $buildRuntime,
|
||||
'adapter' => $adapter,
|
||||
|
||||
@@ -13,6 +13,7 @@ use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Maintenance extends Action
|
||||
{
|
||||
@@ -25,6 +26,7 @@ class Maintenance extends Action
|
||||
{
|
||||
$this
|
||||
->desc('Schedules maintenance tasks and publishes them to our queues')
|
||||
->param('type', 'loop', new WhiteList(['loop', 'trigger']), 'How to run task. "loop" is meant for container entrypoint, and "trigger" for manual execution.')
|
||||
->inject('dbForPlatform')
|
||||
->inject('console')
|
||||
->inject('queueForCertificates')
|
||||
@@ -32,7 +34,7 @@ class Maintenance extends Action
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(Database $dbForPlatform, Document $console, Certificate $queueForCertificates, Delete $queueForDeletes): void
|
||||
public function action(string $type, Database $dbForPlatform, Document $console, Certificate $queueForCertificates, Delete $queueForDeletes): void
|
||||
{
|
||||
Console::title('Maintenance V1');
|
||||
Console::success(APP_NAME . ' maintenance process v1 has started');
|
||||
@@ -57,9 +59,7 @@ class Maintenance extends Action
|
||||
$delay = $next->getTimestamp() - $now->getTimestamp();
|
||||
}
|
||||
|
||||
Console::info('Setting loop start time to ' . $next->format("Y-m-d H:i:s.v") . '. Delaying for ' . $delay . ' seconds.');
|
||||
|
||||
Console::loop(function () use ($interval, $cacheRetention, $schedulesDeletionRetention, $usageStatsRetentionHourly, $dbForPlatform, $console, $queueForDeletes, $queueForCertificates) {
|
||||
$action = function () use ($interval, $cacheRetention, $schedulesDeletionRetention, $usageStatsRetentionHourly, $dbForPlatform, $console, $queueForDeletes, $queueForCertificates) {
|
||||
$time = DatabaseDateTime::now();
|
||||
|
||||
Console::info("[{$time}] Notifying workers with maintenance tasks every {$interval} seconds");
|
||||
@@ -96,7 +96,17 @@ class Maintenance extends Action
|
||||
$this->notifyDeleteCache($cacheRetention, $queueForDeletes);
|
||||
$this->notifyDeleteSchedules($schedulesDeletionRetention, $queueForDeletes);
|
||||
$this->notifyDeleteCSVExports($queueForDeletes);
|
||||
}, $interval, $delay);
|
||||
};
|
||||
|
||||
if ($type === 'loop') {
|
||||
Console::info('Setting loop start time to ' . $next->format("Y-m-d H:i:s.v") . '. Delaying for ' . $delay . ' seconds.');
|
||||
|
||||
Console::loop(function () use ($action) {
|
||||
$action();
|
||||
}, $interval, $delay);
|
||||
} elseif ($type === 'trigger') {
|
||||
$action();
|
||||
}
|
||||
}
|
||||
|
||||
private function notifyDeleteConnections(Delete $queueForDeletes): void
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Workers;
|
||||
use Appwrite\Certificates\Adapter as CertificatesAdapter;
|
||||
use Appwrite\Deletes\Identities;
|
||||
use Appwrite\Deletes\Targets;
|
||||
use Appwrite\Event\Delete as DeleteEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Executor\Executor;
|
||||
use Throwable;
|
||||
@@ -63,6 +64,7 @@ class Deletes extends Action
|
||||
->inject('executionRetention')
|
||||
->inject('auditRetention')
|
||||
->inject('log')
|
||||
->inject('queueForDeletes')
|
||||
->inject('getAudit')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
@@ -87,6 +89,7 @@ class Deletes extends Action
|
||||
string $executionRetention,
|
||||
string $auditRetention,
|
||||
Log $log,
|
||||
DeleteEvent $queueForDeletes,
|
||||
callable $getAudit,
|
||||
): void {
|
||||
$payload = $message->getPayload() ?? [];
|
||||
@@ -191,6 +194,7 @@ class Deletes extends Action
|
||||
$this->deleteUsageStats($project, $getProjectDB, $getLogsDB, $hourlyUsageRetentionDatetime);
|
||||
$this->deleteExpiredSessions($project, $getProjectDB);
|
||||
$this->deleteExpiredTransactions($project, $getProjectDB);
|
||||
$this->deleteOldDeployments($queueForDeletes, $project, $getProjectDB);
|
||||
break;
|
||||
default:
|
||||
throw new \Exception('No delete operation for type: ' . \strval($type));
|
||||
@@ -310,6 +314,61 @@ class Deletes extends Action
|
||||
Targets::delete($getProjectDB($project), Query::equal('sessionInternalId', [$session->getSequence()]));
|
||||
}
|
||||
|
||||
private function deleteOldDeployments(DeleteEvent $queueForDeletes, Document $project, callable $getProjectDB): void
|
||||
{
|
||||
/** @var Database $dbForProject */
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
$removalCallback = function (Document $resource) use ($dbForProject, $queueForDeletes, $project) {
|
||||
$retention = $resource->getAttribute('deploymentRetention', 0);
|
||||
|
||||
// 0 means unlimited - never delete
|
||||
if ($retention === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$activeDeploymentId = $resource->getAttribute('deploymentId', '');
|
||||
|
||||
$queries = [
|
||||
Query::createdBefore(DateTime::addSeconds(new \DateTime(), -1 * $retention * 24 * 60 * 60)),
|
||||
Query::equal('resourceInternalId', [$resource->getSequence()]),
|
||||
Query::equal('resourceType', [$resource->getCollection()]),
|
||||
Query::orderDesc('$createdAt'),
|
||||
];
|
||||
|
||||
if (!empty($activeDeploymentId)) {
|
||||
$queries[] = Query::notEqual('$id', $activeDeploymentId);
|
||||
}
|
||||
|
||||
$this->deleteByGroup(
|
||||
'deployments',
|
||||
$queries,
|
||||
$dbForProject,
|
||||
function (Document $deployment) use ($queueForDeletes, $project) {
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($deployment)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$this->listByGroup(
|
||||
'functions',
|
||||
[],
|
||||
$dbForProject,
|
||||
$removalCallback
|
||||
);
|
||||
|
||||
$this->listByGroup(
|
||||
'sites',
|
||||
[],
|
||||
$dbForProject,
|
||||
$removalCallback
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Document $project
|
||||
* @param callable $getProjectDB
|
||||
|
||||
@@ -368,7 +368,7 @@ class Functions extends Action
|
||||
$user ??= new Document();
|
||||
$functionId = $function->getId();
|
||||
$deploymentId = $function->getAttribute('deploymentId', '');
|
||||
$spec = Config::getParam('specifications')[$function->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
$spec = Config::getParam('specifications')[$function->getAttribute('runtimeSpecification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
|
||||
$log->addTag('deploymentId', $deploymentId);
|
||||
|
||||
@@ -533,6 +533,11 @@ class Functions extends Action
|
||||
try {
|
||||
$version = $function->getAttribute('version', 'v2');
|
||||
$command = $runtime['startCommand'];
|
||||
|
||||
if (!empty($deployment->getAttribute('startCommand', ''))) {
|
||||
$command = 'cd /usr/local/server/src/function/ && ' . $deployment->getAttribute('startCommand', '');
|
||||
}
|
||||
|
||||
$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\"";
|
||||
|
||||
@@ -6,7 +6,7 @@ use Appwrite\Utopia\Request\Filter;
|
||||
|
||||
class V21 extends Filter
|
||||
{
|
||||
// Convert 1.8.0 params to 1.8.1
|
||||
// Convert 1.8.0 params to 1.9.0
|
||||
public function parse(array $content, string $model): array
|
||||
{
|
||||
switch ($model) {
|
||||
@@ -14,6 +14,12 @@ class V21 extends Filter
|
||||
case 'sites.createTemplateDeployment':
|
||||
$content = $this->convertVersionToTypeAndReference($content);
|
||||
break;
|
||||
case 'functions.create':
|
||||
case 'sites.create':
|
||||
case 'functions.update':
|
||||
case 'sites.update':
|
||||
$content = $this->convertSpecs($content);
|
||||
break;
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
@@ -31,4 +37,15 @@ class V21 extends Filter
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function convertSpecs(array $content): array
|
||||
{
|
||||
if (!empty($content['specification'])) {
|
||||
$content['buildSpecification'] = $content['specification'];
|
||||
$content['runtimeSpecification'] = $content['specification'];
|
||||
unset($content['specification']);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Filters;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filter;
|
||||
|
||||
// Convert 1.9 Data format to 1.8 format
|
||||
class V21 extends Filter
|
||||
{
|
||||
public function parse(array $content, string $model): array
|
||||
{
|
||||
$parsedResponse = $content;
|
||||
|
||||
return match ($model) {
|
||||
Response::MODEL_SITE => $this->parseSite($content),
|
||||
Response::MODEL_SITE_LIST => $this->handleList(
|
||||
$content,
|
||||
"sites",
|
||||
fn ($item) => $this->parseSite($item),
|
||||
),
|
||||
Response::MODEL_FUNCTION => $this->parseFunction($content),
|
||||
Response::MODEL_FUNCTION_LIST => $this->handleList(
|
||||
$content,
|
||||
"functions",
|
||||
fn ($item) => $this->parseFunction($item),
|
||||
),
|
||||
default => $parsedResponse,
|
||||
};
|
||||
}
|
||||
|
||||
protected function parseSite(array $content): array
|
||||
{
|
||||
$content = $this->parseSpecs($content);
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function parseFunction(array $content): array
|
||||
{
|
||||
$content = $this->parseSpecs($content);
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function parseSpecs(array $content): array
|
||||
{
|
||||
$content['specification'] = $content['buildSpecification'] ?? null;
|
||||
unset($content['buildSpecification']);
|
||||
unset($content['runtimeSpecification']);
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,12 @@ class Func extends Model
|
||||
'default' => '',
|
||||
'example' => 'python-3.8',
|
||||
])
|
||||
->addRule('deploymentRetention', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'How many days to keep the non-active deployments before they will be automatically deleted.',
|
||||
'default' => 0,
|
||||
'example' => 7,
|
||||
])
|
||||
->addRule('deploymentId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Function\'s active deployment ID.',
|
||||
@@ -176,9 +182,15 @@ class Func extends Model
|
||||
'default' => false,
|
||||
'example' => false,
|
||||
])
|
||||
->addRule('specification', [
|
||||
->addRule('buildSpecification', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Machine specification for builds and executions.',
|
||||
'description' => 'Machine specification for deployment builds.',
|
||||
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
'example' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
])
|
||||
->addRule('runtimeSpecification', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Machine specification for executions.',
|
||||
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
'example' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
])
|
||||
|
||||
@@ -58,6 +58,12 @@ class Site extends Model
|
||||
'default' => '',
|
||||
'example' => 'react',
|
||||
])
|
||||
->addRule('deploymentRetention', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'How many days to keep the non-active deployments before they will be automatically deleted.',
|
||||
'default' => 0,
|
||||
'example' => 7,
|
||||
])
|
||||
->addRule('deploymentId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Site\'s active deployment ID.',
|
||||
@@ -125,6 +131,12 @@ class Site extends Model
|
||||
'default' => '',
|
||||
'example' => 'npm run build',
|
||||
])
|
||||
->addRule('startCommand', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Custom command to use when starting site runtime.',
|
||||
'default' => '',
|
||||
'example' => 'node custom-server.mjs',
|
||||
])
|
||||
->addRule('outputDirectory', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The directory where the site build output is located.',
|
||||
@@ -161,9 +173,15 @@ class Site extends Model
|
||||
'default' => false,
|
||||
'example' => false,
|
||||
])
|
||||
->addRule('specification', [
|
||||
->addRule('buildSpecification', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Machine specification for builds and executions.',
|
||||
'description' => 'Machine specification for deployment builds.',
|
||||
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
'example' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
])
|
||||
->addRule('runtimeSpecification', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Machine specification for SSR executions.',
|
||||
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
'example' => APP_COMPUTE_SPECIFICATION_DEFAULT,
|
||||
])
|
||||
|
||||
@@ -939,7 +939,8 @@ class UsageTest extends Scope
|
||||
],
|
||||
'schedule' => '0 0 1 1 *',
|
||||
'timeout' => 10,
|
||||
'specification' => Specification::S_8VCPU_8GB
|
||||
'buildSpecification' => Specification::S_8VCPU_8GB,
|
||||
'runtimeSpecification' => Specification::S_4VCPU_4GB,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
|
||||
@@ -537,4 +538,58 @@ class FunctionsConsoleClientTest extends Scope
|
||||
|
||||
$this->cleanupFunction($functionId);
|
||||
}
|
||||
|
||||
public function testFunctionDeploymentRetentionWithMaintenance(): void
|
||||
{
|
||||
$functionId = $this->setupFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test retention function',
|
||||
'runtime' => 'node-22',
|
||||
'entrypoint' => 'index.js',
|
||||
'deploymentRetention' => 180
|
||||
]);
|
||||
$this->assertNotEmpty($functionId);
|
||||
|
||||
$deploymentIdInactive = $this->setupDeployment($functionId, [
|
||||
'code' => $this->packageFunction('node'),
|
||||
'activate' => true
|
||||
]);
|
||||
$this->assertNotEmpty($deploymentIdInactive);
|
||||
|
||||
$deploymentIdInactiveOld = $this->setupDeployment($functionId, [
|
||||
'code' => $this->packageFunction('node'),
|
||||
'activate' => true
|
||||
]);
|
||||
$this->assertNotEmpty($deploymentIdInactiveOld);
|
||||
|
||||
$deploymentIdActive = $this->setupDeployment($functionId, [
|
||||
'code' => $this->packageFunction('node'),
|
||||
'activate' => true
|
||||
]);
|
||||
$this->assertNotEmpty($deploymentIdActive);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/mock/time-travels', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'resourceType' => 'deployment',
|
||||
'resourceId' => $deploymentIdInactiveOld,
|
||||
'createdAt' => '2020-01-01T00:00:00Z' // More than 180 days ago
|
||||
]);
|
||||
$this->assertSame(204, $response['headers']['status-code']);
|
||||
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$code = Console::execute("docker exec appwrite-task-maintenance maintenance --type=trigger", '', $stdout, $stderr);
|
||||
$this->assertSame(0, $code, "Maintenance command failed with code $code: $stderr ($stdout)");
|
||||
|
||||
$this->assertEventually(function () use ($functionId) {
|
||||
$response = $this->listDeployments($functionId);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(2, $response['body']['total']);
|
||||
});
|
||||
|
||||
$this->cleanupFunction($functionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,14 +36,17 @@ class FunctionsCustomServerTest extends Scope
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Specs function',
|
||||
'runtime' => 'node-22',
|
||||
'specification' => $specifications['body']['specifications'][0]['slug']
|
||||
'buildSpecification' => $specifications['body']['specifications'][0]['slug'],
|
||||
'runtimeSpecification' => $specifications['body']['specifications'][1]['slug'],
|
||||
]);
|
||||
$this->assertEquals(201, $function['headers']['status-code']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $function['body']['specification']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $function['body']['buildSpecification']);
|
||||
$this->assertEquals($specifications['body']['specifications'][1]['slug'], $function['body']['runtimeSpecification']);
|
||||
|
||||
$function = $this->getFunction($function['body']['$id']);
|
||||
$this->assertEquals(200, $function['headers']['status-code']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $function['body']['specification']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $function['body']['buildSpecification']);
|
||||
$this->assertEquals($specifications['body']['specifications'][1]['slug'], $function['body']['runtimeSpecification']);
|
||||
|
||||
$this->cleanupFunction($function['body']['$id']);
|
||||
|
||||
@@ -51,7 +54,15 @@ class FunctionsCustomServerTest extends Scope
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Specs function',
|
||||
'runtime' => 'node-22',
|
||||
'specification' => 'cheap-please'
|
||||
'buildSpecification' => 'cheap-please'
|
||||
]);
|
||||
$this->assertEquals(400, $function['headers']['status-code']);
|
||||
|
||||
$function = $this->createFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Specs function',
|
||||
'runtime' => 'node-22',
|
||||
'runtimeSpecification' => 'cheap-please'
|
||||
]);
|
||||
$this->assertEquals(400, $function['headers']['status-code']);
|
||||
}
|
||||
@@ -1364,12 +1375,12 @@ class FunctionsCustomServerTest extends Scope
|
||||
'timeout' => 15,
|
||||
'runtime' => 'node-22',
|
||||
'entrypoint' => 'index.js',
|
||||
'specification' => Specification::S_1VCPU_1GB,
|
||||
'runtimeSpecification' => Specification::S_1VCPU_1GB,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $function['headers']['status-code']);
|
||||
$this->assertNotEmpty($function['body']['$id']);
|
||||
$this->assertEquals(Specification::S_1VCPU_1GB, $function['body']['specification']);
|
||||
$this->assertEquals(Specification::S_1VCPU_1GB, $function['body']['runtimeSpecification']);
|
||||
|
||||
// Verify the updated specs
|
||||
$execution = $this->createExecution($data['functionId']);
|
||||
@@ -1398,12 +1409,12 @@ class FunctionsCustomServerTest extends Scope
|
||||
'timeout' => 15,
|
||||
'runtime' => 'node-22',
|
||||
'entrypoint' => 'index.js',
|
||||
'specification' => Specification::S_1VCPU_512MB,
|
||||
'runtimeSpecification' => Specification::S_1VCPU_512MB,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $function['headers']['status-code']);
|
||||
$this->assertNotEmpty($function['body']['$id']);
|
||||
$this->assertEquals(Specification::S_1VCPU_512MB, $function['body']['specification']);
|
||||
$this->assertEquals(Specification::S_1VCPU_512MB, $function['body']['runtimeSpecification']);
|
||||
|
||||
// Verify the updated specs
|
||||
$execution = $this->createExecution($data['functionId']);
|
||||
@@ -1428,11 +1439,29 @@ class FunctionsCustomServerTest extends Scope
|
||||
'timeout' => 15,
|
||||
'runtime' => 'node-22',
|
||||
'entrypoint' => 'index.js',
|
||||
'specification' => 's-2vcpu-512mb', // Invalid specification
|
||||
'buildSpecification' => 's-2vcpu-512mb', // Invalid specification
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $function['headers']['status-code']);
|
||||
$this->assertStringStartsWith('Invalid `specification` param: Specification must be one of:', $function['body']['message']);
|
||||
$this->assertStringStartsWith('Invalid `buildSpecification` param: Specification must be one of:', $function['body']['message']);
|
||||
|
||||
$function = $this->client->call(Client::METHOD_PUT, '/functions/' . $data['functionId'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Test1',
|
||||
'events' => [
|
||||
'users.*.update.name',
|
||||
'users.*.update.email',
|
||||
],
|
||||
'timeout' => 15,
|
||||
'runtime' => 'node-22',
|
||||
'entrypoint' => 'index.js',
|
||||
'runtimeSpecification' => 's-2vcpu-512mb', // Invalid specification
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $function['headers']['status-code']);
|
||||
$this->assertStringStartsWith('Invalid `runtimeSpecification` param: Specification must be one of:', $function['body']['message']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
@@ -2015,6 +2044,8 @@ class FunctionsCustomServerTest extends Scope
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertArrayNotHasKey('scopes', $response['body']);
|
||||
$this->assertArrayNotHasKey('specification', $response['body']);
|
||||
$this->assertArrayNotHasKey('buildSpecification', $response['body']);
|
||||
$this->assertArrayNotHasKey('runtimeSpecification', $response['body']);
|
||||
|
||||
// get function with 1.5.0 response format header
|
||||
$function = $this->client->call(Client::METHOD_GET, '/functions/' . $response['body']['$id'], array_merge([
|
||||
@@ -2025,13 +2056,31 @@ class FunctionsCustomServerTest extends Scope
|
||||
|
||||
$this->assertEquals(200, $function['headers']['status-code']);
|
||||
$this->assertArrayNotHasKey('scopes', $function['body']);
|
||||
$this->assertArrayNotHasKey('buildSpecification', $function['body']);
|
||||
$this->assertArrayNotHasKey('runtimeSpecification', $function['body']);
|
||||
$this->assertArrayNotHasKey('specification', $function['body']);
|
||||
|
||||
$function = $this->getFunction($function['body']['$id']);
|
||||
// get function with 1.8.0 response format header
|
||||
$function = $this->client->call(Client::METHOD_GET, '/functions/' . $response['body']['$id'], array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0', // add response format header
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $function['headers']['status-code']);
|
||||
$this->assertArrayHasKey('scopes', $function['body']);
|
||||
$this->assertArrayHasKey('specification', $function['body']);
|
||||
$this->assertArrayNotHasKey('buildSpecification', $function['body']);
|
||||
$this->assertArrayNotHasKey('runtimeSpecification', $function['body']);
|
||||
|
||||
// get function with latest version
|
||||
$function = $this->getFunction($function['body']['$id']);
|
||||
|
||||
$this->assertEquals(200, $function['headers']['status-code']);
|
||||
$this->assertArrayHasKey('scopes', $function['body']);
|
||||
$this->assertArrayNotHasKey('specification', $function['body']);
|
||||
$this->assertArrayHasKey('buildSpecification', $function['body']);
|
||||
$this->assertArrayHasKey('runtimeSpecification', $function['body']);
|
||||
|
||||
$functionId = $function['body']['$id'] ?? '';
|
||||
$this->cleanupFunction($functionId);
|
||||
@@ -2181,15 +2230,17 @@ class FunctionsCustomServerTest extends Scope
|
||||
'entrypoint' => 'index.js',
|
||||
'logging' => false,
|
||||
'execute' => ['any'],
|
||||
'specification' => Specification::S_2VCPU_2GB,
|
||||
'buildSpecification' => Specification::S_2VCPU_2GB,
|
||||
'runtimeSpecification' => Specification::S_1VCPU_1GB,
|
||||
'commands' => 'echo $APPWRITE_FUNCTION_MEMORY:$APPWRITE_FUNCTION_CPUS',
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $function['headers']['status-code']);
|
||||
$this->assertEquals(Specification::S_2VCPU_2GB, $function['body']['specification']);
|
||||
$this->assertEquals(Specification::S_2VCPU_2GB, $function['body']['buildSpecification']);
|
||||
$this->assertEquals(Specification::S_1VCPU_1GB, $function['body']['runtimeSpecification']);
|
||||
$this->assertNotEmpty($function['body']['$id']);
|
||||
|
||||
$functionId = $functionId = $function['body']['$id'] ?? '';
|
||||
$functionId = $function['body']['$id'] ?? '';
|
||||
|
||||
$deploymentId = $this->setupDeployment($functionId, [
|
||||
'code' => $this->packageFunction('basic'),
|
||||
@@ -2208,8 +2259,8 @@ class FunctionsCustomServerTest extends Scope
|
||||
$this->assertNotEmpty($execution['body']['$id']);
|
||||
|
||||
$executionResponse = json_decode($execution['body']['responseBody'], true);
|
||||
$this->assertEquals('2048', $executionResponse['APPWRITE_FUNCTION_MEMORY']);
|
||||
$this->assertEquals('2', $executionResponse['APPWRITE_FUNCTION_CPUS']);
|
||||
$this->assertEquals('1024', $executionResponse['APPWRITE_FUNCTION_MEMORY']);
|
||||
$this->assertEquals('1', $executionResponse['APPWRITE_FUNCTION_CPUS']);
|
||||
|
||||
$this->cleanupFunction($functionId);
|
||||
}
|
||||
@@ -2578,4 +2629,119 @@ class FunctionsCustomServerTest extends Scope
|
||||
|
||||
$this->cleanupFunction($functionId);
|
||||
}
|
||||
|
||||
public function testFunctionDeploymentRetention(): void
|
||||
{
|
||||
$functionIds = [];
|
||||
|
||||
// Default
|
||||
$response = $this->createFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test retention function',
|
||||
'runtime' => 'node-22',
|
||||
]);
|
||||
$this->assertSame(201, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
$functionIds[] = $response['body']['$id'];
|
||||
|
||||
$response = $this->getFunction($response['body']['$id']);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
|
||||
// Success values
|
||||
$response = $this->createFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test retention function',
|
||||
'runtime' => 'node-22',
|
||||
'deploymentRetention' => 0
|
||||
]);
|
||||
$this->assertSame(201, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
$functionIds[] = $response['body']['$id'];
|
||||
|
||||
$response = $this->getFunction($response['body']['$id']);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
|
||||
$response = $this->createFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test retention function',
|
||||
'runtime' => 'node-22',
|
||||
'deploymentRetention' => 180
|
||||
]);
|
||||
$this->assertSame(201, $response['headers']['status-code']);
|
||||
$this->assertSame(180, $response['body']['deploymentRetention']);
|
||||
$functionIds[] = $response['body']['$id'];
|
||||
|
||||
$response = $this->getFunction($response['body']['$id']);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(180, $response['body']['deploymentRetention']);
|
||||
|
||||
// Failure values
|
||||
$response = $this->createFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test retention function',
|
||||
'runtime' => 'node-22',
|
||||
'deploymentRetention' => 999999
|
||||
]);
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->createFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test retention function',
|
||||
'runtime' => 'node-22',
|
||||
'deploymentRetention' => -1
|
||||
]);
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
|
||||
// Update flow
|
||||
$response = $this->createFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test retention function',
|
||||
'runtime' => 'node-22',
|
||||
'deploymentRetention' => 180
|
||||
]);
|
||||
$this->assertSame(201, $response['headers']['status-code']);
|
||||
$this->assertSame(180, $response['body']['deploymentRetention']);
|
||||
$functionIds[] = $response['body']['$id'];
|
||||
$functionIdForUpdate = $response['body']['$id'];
|
||||
|
||||
$response = $this->updateFunction($functionIdForUpdate, [
|
||||
'name' => 'Test retention function',
|
||||
'deploymentRetention' => 90
|
||||
]);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(90, $response['body']['deploymentRetention']);
|
||||
|
||||
$response = $this->getFunction($functionIdForUpdate);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(90, $response['body']['deploymentRetention']);
|
||||
|
||||
$response = $this->updateFunction($functionIdForUpdate, [
|
||||
'name' => 'Test retention function',
|
||||
]);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
|
||||
$response = $this->getFunction($functionIdForUpdate);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
|
||||
// Failed update flow
|
||||
$response = $this->updateFunction($functionIdForUpdate, [
|
||||
'name' => 'Test retention function',
|
||||
'deploymentRetention' => -1
|
||||
]);
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->updateFunction($functionIdForUpdate, [
|
||||
'name' => 'Test retention function',
|
||||
'deploymentRetention' => 999999
|
||||
]);
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
|
||||
foreach ($functionIds as $functionId) {
|
||||
$this->cleanupFunction($functionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
class SitesConsoleClientTest extends Scope
|
||||
@@ -196,7 +197,59 @@ class SitesConsoleClientTest extends Scope
|
||||
$this->assertEquals(200, $site['headers']['status-code']);
|
||||
$this->assertEmpty($site['body']['deploymentScreenshotLight']);
|
||||
$this->assertEmpty($site['body']['deploymentScreenshotDark']);
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testSiteDeploymentRetentionWithMaintenance(): void
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'deploymentRetention' => 180,
|
||||
'buildRuntime' => 'node-22',
|
||||
]);
|
||||
$this->assertNotEmpty($siteId);
|
||||
|
||||
$deploymentIdInactive = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('static'),
|
||||
'activate' => true
|
||||
]);
|
||||
$this->assertNotEmpty($deploymentIdInactive);
|
||||
|
||||
$deploymentIdInactiveOld = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('static'),
|
||||
'activate' => true
|
||||
]);
|
||||
$this->assertNotEmpty($deploymentIdInactiveOld);
|
||||
|
||||
$deploymentIdActive = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('static'),
|
||||
'activate' => true
|
||||
]);
|
||||
$this->assertNotEmpty($deploymentIdActive);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/mock/time-travels', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'resourceType' => 'deployment',
|
||||
'resourceId' => $deploymentIdInactiveOld,
|
||||
'createdAt' => '2020-01-01T00:00:00Z' // More than 180 days ago
|
||||
]);
|
||||
$this->assertSame(204, $response['headers']['status-code']);
|
||||
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
$code = Console::execute("docker exec appwrite-task-maintenance maintenance --type=trigger", '', $stdout, $stderr);
|
||||
$this->assertSame(0, $code, "Maintenance command failed with code $code: $stderr ($stdout)");
|
||||
|
||||
$this->assertEventually(function () use ($siteId) {
|
||||
$response = $this->listDeployments($siteId);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(2, $response['body']['total']);
|
||||
});
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,14 +38,17 @@ class SitesCustomServerTest extends Scope
|
||||
'framework' => 'other',
|
||||
'name' => 'Specs site',
|
||||
'siteId' => ID::unique(),
|
||||
'specification' => $specifications['body']['specifications'][0]['slug']
|
||||
'buildSpecification' => $specifications['body']['specifications'][0]['slug'],
|
||||
'runtimeSpecification' => $specifications['body']['specifications'][1]['slug'],
|
||||
]);
|
||||
$this->assertEquals(201, $site['headers']['status-code']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $site['body']['specification']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $site['body']['buildSpecification']);
|
||||
$this->assertEquals($specifications['body']['specifications'][1]['slug'], $site['body']['runtimeSpecification']);
|
||||
|
||||
$site = $this->getSite($site['body']['$id']);
|
||||
$this->assertEquals(200, $site['headers']['status-code']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $site['body']['specification']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $site['body']['buildSpecification']);
|
||||
$this->assertEquals($specifications['body']['specifications'][1]['slug'], $site['body']['runtimeSpecification']);
|
||||
|
||||
$this->cleanupSite($site['body']['$id']);
|
||||
|
||||
@@ -54,7 +57,16 @@ class SitesCustomServerTest extends Scope
|
||||
'framework' => 'other',
|
||||
'name' => 'Specs site',
|
||||
'siteId' => ID::unique(),
|
||||
'specification' => 'cheap-please'
|
||||
'buildSpecification' => 'cheap-please'
|
||||
]);
|
||||
$this->assertEquals(400, $site['headers']['status-code']);
|
||||
|
||||
$site = $this->createSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'framework' => 'other',
|
||||
'name' => 'Specs site',
|
||||
'siteId' => ID::unique(),
|
||||
'runtimeSpecification' => 'cheap-please'
|
||||
]);
|
||||
$this->assertEquals(400, $site['headers']['status-code']);
|
||||
}
|
||||
@@ -1349,12 +1361,12 @@ class SitesCustomServerTest extends Scope
|
||||
'providerBranch' => 'main',
|
||||
'providerRootDirectory' => './',
|
||||
'$id' => $siteId,
|
||||
'specification' => Specification::S_1VCPU_1GB,
|
||||
'runtimeSpecification' => Specification::S_1VCPU_1GB,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $site['headers']['status-code']);
|
||||
$this->assertNotEmpty($site['body']['$id']);
|
||||
$this->assertEquals(Specification::S_1VCPU_1GB, $site['body']['specification']);
|
||||
$this->assertEquals(Specification::S_1VCPU_1GB, $site['body']['runtimeSpecification']);
|
||||
|
||||
// Change the specs to 1vcpu 512mb
|
||||
$site = $this->updateSite([
|
||||
@@ -1366,12 +1378,12 @@ class SitesCustomServerTest extends Scope
|
||||
'providerBranch' => 'main',
|
||||
'providerRootDirectory' => './',
|
||||
'$id' => $siteId,
|
||||
'specification' => Specification::S_1VCPU_512MB,
|
||||
'runtimeSpecification' => Specification::S_1VCPU_512MB,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $site['headers']['status-code']);
|
||||
$this->assertNotEmpty($site['body']['$id']);
|
||||
$this->assertEquals(Specification::S_1VCPU_512MB, $site['body']['specification']);
|
||||
$this->assertEquals(Specification::S_1VCPU_512MB, $site['body']['runtimeSpecification']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
@@ -1386,11 +1398,26 @@ class SitesCustomServerTest extends Scope
|
||||
'providerBranch' => 'main',
|
||||
'providerRootDirectory' => './',
|
||||
'$id' => $siteId,
|
||||
'specification' => 's-2vcpu-512mb', // Invalid specification
|
||||
'buildSpecification' => 's-2vcpu-512mb', // Invalid specification
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $site['headers']['status-code']);
|
||||
$this->assertStringStartsWith('Invalid `specification` param: Specification must be one of:', $site['body']['message']);
|
||||
$this->assertStringStartsWith('Invalid `buildSpecification` param: Specification must be one of:', $site['body']['message']);
|
||||
|
||||
$site = $this->updateSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
'providerBranch' => 'main',
|
||||
'providerRootDirectory' => './',
|
||||
'$id' => $siteId,
|
||||
'runtimeSpecification' => 's-2vcpu-512mb', // Invalid specification
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $site['headers']['status-code']);
|
||||
$this->assertStringStartsWith('Invalid `runtimeSpecification` param: Specification must be one of:', $site['body']['message']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
@@ -2999,4 +3026,244 @@ class SitesCustomServerTest extends Scope
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testSiteCustomStartCommand(): void
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Astro site',
|
||||
'framework' => 'astro',
|
||||
'adapter' => 'ssr',
|
||||
'startCommand' => 'node custom-server.js',
|
||||
'buildRuntime' => 'node-22',
|
||||
'outputDirectory' => './dist',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
'fallbackFile' => '',
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($siteId);
|
||||
|
||||
$domain = $this->setupSiteDomain($siteId);
|
||||
|
||||
$deploymentId = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('astro-custom-start-command'),
|
||||
'activate' => 'true'
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$domain = $this->getSiteDomain($siteId);
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Homepage OK", $response['body']);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/ssr');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("SSR OK", $response['body']);
|
||||
$originalBody = $response['body'];
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/ssr');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("SSR OK", $response['body']);
|
||||
$this->assertNotEquals($originalBody, $response['body']); // Includes Date.now()
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/ssr-custom');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Custom SSR OK", $response['body']);
|
||||
$originalBody = $response['body'];
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/ssr-custom');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Custom SSR OK", $response['body']);
|
||||
$this->assertNotEquals($originalBody, $response['body']); // Includes Date.now()
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/non-existing');
|
||||
$this->assertEquals(500, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Custom error", $response['body']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testSiteSpecifications()
|
||||
{
|
||||
// Check if the site specifications are correctly set in builds
|
||||
$site = $this->createSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Astro site',
|
||||
'framework' => 'astro',
|
||||
'adapter' => 'ssr',
|
||||
'buildRuntime' => 'node-22',
|
||||
'outputDirectory' => './dist',
|
||||
'buildCommand' => 'npm run build && echo $APPWRITE_SITE_MEMORY:$APPWRITE_SITE_CPUS',
|
||||
'installCommand' => 'npm install',
|
||||
'fallbackFile' => '',
|
||||
'buildSpecification' => Specification::S_2VCPU_2GB,
|
||||
'runtimeSpecification' => Specification::S_1VCPU_1GB,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $site['headers']['status-code']);
|
||||
$this->assertEquals(Specification::S_2VCPU_2GB, $site['body']['buildSpecification']);
|
||||
$this->assertEquals(Specification::S_1VCPU_1GB, $site['body']['runtimeSpecification']);
|
||||
$this->assertNotEmpty($site['body']['$id']);
|
||||
|
||||
$siteId = $site['body']['$id'] ?? '';
|
||||
|
||||
$domain = $this->setupSiteDomain($siteId);
|
||||
|
||||
$deploymentId = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('astro'),
|
||||
'activate' => true
|
||||
]);
|
||||
|
||||
$this->assertEventually(function () use ($siteId, $deploymentId) {
|
||||
$deployment = $this->getDeployment($siteId, $deploymentId);
|
||||
\var_dump($deployment['body']['buildLogs']);
|
||||
$this->assertStringContainsString('2048:2', $deployment['body']['buildLogs']);
|
||||
}, 10000, 500);
|
||||
|
||||
// Check if the sites specifications are correctly set in executions
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/specs');
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertEquals('1024', $response['body']['APPWRITE_SITE_MEMORY']);
|
||||
$this->assertEquals('1', $response['body']['APPWRITE_SITE_CPUS']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testSiteDeploymentRetention(): void
|
||||
{
|
||||
$siteIds = [];
|
||||
|
||||
// Default
|
||||
$response = $this->createSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'buildRuntime' => 'node-22',
|
||||
]);
|
||||
$this->assertSame(201, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
$siteIds[] = $response['body']['$id'];
|
||||
|
||||
$response = $this->getSite($response['body']['$id']);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
|
||||
// Success values
|
||||
$response = $this->createSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'buildRuntime' => 'node-22',
|
||||
'deploymentRetention' => 0
|
||||
]);
|
||||
$this->assertSame(201, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
$siteIds[] = $response['body']['$id'];
|
||||
|
||||
$response = $this->getSite($response['body']['$id']);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
|
||||
$response = $this->createSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'buildRuntime' => 'node-22',
|
||||
'deploymentRetention' => 180
|
||||
]);
|
||||
$this->assertSame(201, $response['headers']['status-code']);
|
||||
$this->assertSame(180, $response['body']['deploymentRetention']);
|
||||
$siteIds[] = $response['body']['$id'];
|
||||
|
||||
$response = $this->getSite($response['body']['$id']);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(180, $response['body']['deploymentRetention']);
|
||||
|
||||
// Failure values
|
||||
$response = $this->createSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'buildRuntime' => 'node-22',
|
||||
'deploymentRetention' => 999999
|
||||
]);
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->createSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'buildRuntime' => 'node-22',
|
||||
'deploymentRetention' => -1
|
||||
]);
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
|
||||
// Update flow
|
||||
$response = $this->createSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'buildRuntime' => 'node-22',
|
||||
'deploymentRetention' => 180
|
||||
]);
|
||||
$this->assertSame(201, $response['headers']['status-code']);
|
||||
$this->assertSame(180, $response['body']['deploymentRetention']);
|
||||
$siteIds[] = $response['body']['$id'];
|
||||
$siteIdToUpdate = $response['body']['$id'];
|
||||
|
||||
$response = $this->updateSite([
|
||||
'$id' => $siteIdToUpdate,
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'deploymentRetention' => 90
|
||||
]);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(90, $response['body']['deploymentRetention']);
|
||||
|
||||
$response = $this->getSite($siteIdToUpdate);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(90, $response['body']['deploymentRetention']);
|
||||
|
||||
$response = $this->updateSite([
|
||||
'$id' => $siteIdToUpdate,
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
]);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
|
||||
$response = $this->getSite($siteIdToUpdate);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertSame(0, $response['body']['deploymentRetention']);
|
||||
|
||||
// Failed update flow
|
||||
$response = $this->updateSite([
|
||||
'$id' => $siteIdToUpdate,
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'deploymentRetention' => -1
|
||||
]);
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->updateSite([
|
||||
'$id' => $siteIdToUpdate,
|
||||
'name' => 'Test retention site',
|
||||
'framework' => 'other',
|
||||
'deploymentRetention' => 999999
|
||||
]);
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
|
||||
foreach ($siteIds as $siteId) {
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# build output
|
||||
dist/
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
@@ -0,0 +1,10 @@
|
||||
// @ts-check
|
||||
import { defineConfig } from 'astro/config';
|
||||
import node from '@astrojs/node';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
adapter: node({
|
||||
mode: 'middleware',
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import express from 'express';
|
||||
import { handler as ssrHandler } from './server/entry.mjs';
|
||||
|
||||
const app = express();
|
||||
const base = '/';
|
||||
app.use(base, express.static('client/'));
|
||||
app.get('/ssr-custom', (_req, res) => {
|
||||
res.send('Custom SSR OK (' + Date.now() + ')');
|
||||
});
|
||||
app.use(ssrHandler);
|
||||
|
||||
app.use((_req, res) => {
|
||||
res.status(500).send('Custom error');
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "astro-custom-start-command",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build && cp custom-server.js dist/custom-server.js && rm -rf node_modules && npm install --production",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/node": "^9.5.1",
|
||||
"express": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "^5.15.9"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>Astro</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Homepage OK</h1>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,5 @@
|
||||
export const prerender = false;
|
||||
|
||||
export const GET = async () => {
|
||||
return new Response("SSR OK (" + Date.now() + ")");
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
export async function GET(_context) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
APPWRITE_SITE_MEMORY: process.env.APPWRITE_SITE_MEMORY,
|
||||
APPWRITE_SITE_CPUS: process.env.APPWRITE_SITE_CPUS,
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user