Merge pull request #9469 from appwrite/feat-site-specifications

Feat: Merge specs
This commit is contained in:
Matej Bačo 2025-03-08 10:58:49 +01:00 committed by GitHub
commit 8ff4d60dbb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 209 additions and 224 deletions

View file

@ -1,51 +0,0 @@
<?php
use Appwrite\Functions\Specification;
return [
Specification::S_05VCPU_512MB => [
'slug' => Specification::S_05VCPU_512MB,
'memory' => 512,
'cpus' => 0.5
],
Specification::S_1VCPU_512MB => [
'slug' => Specification::S_1VCPU_512MB,
'memory' => 512,
'cpus' => 1
],
Specification::S_1VCPU_1GB => [
'slug' => Specification::S_1VCPU_1GB,
'memory' => 1024,
'cpus' => 1
],
Specification::S_2VCPU_2GB => [
'slug' => Specification::S_2VCPU_2GB,
'memory' => 2048,
'cpus' => 2
],
Specification::S_2VCPU_4GB => [
'slug' => Specification::S_2VCPU_4GB,
'memory' => 4096,
'cpus' => 2
],
Specification::S_4VCPU_4GB => [
'slug' => Specification::S_4VCPU_4GB,
'memory' => 4096,
'cpus' => 4
],
Specification::S_4VCPU_8GB => [
'slug' => Specification::S_4VCPU_8GB,
'memory' => 8192,
'cpus' => 4
],
Specification::S_8VCPU_4GB => [
'slug' => Specification::S_8VCPU_4GB,
'memory' => 4096,
'cpus' => 8
],
Specification::S_8VCPU_8GB => [
'slug' => Specification::S_8VCPU_8GB,
'memory' => 8192,
'cpus' => 8
]
];

View file

@ -1,6 +1,6 @@
<?php
use Appwrite\Sites\Specification;
use Appwrite\Platform\Modules\Compute\Specification;
return [
Specification::S_05VCPU_512MB => [

View file

@ -174,7 +174,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
};
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
$spec = Config::getParam('runtime-specifications')[$resource->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
$spec = Config::getParam('specifications')[$resource->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
$runtime = match ($type) {
'function' => $runtimes[$resource->getAttribute('runtime')] ?? null,

View file

@ -36,13 +36,13 @@ use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception;
use Appwrite\Functions\Specification;
use Appwrite\GraphQL\Promises\Adapter\Swoole;
use Appwrite\GraphQL\Schema;
use Appwrite\Hooks\Hooks;
use Appwrite\Network\Validator\Email;
use Appwrite\Network\Validator\Origin;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Platform\Modules\Compute\Specification;
use Appwrite\PubSub\Adapter\Redis as PubSub;
use Appwrite\URL\URL as AppwriteURL;
use Appwrite\Utopia\Request;
@ -389,8 +389,7 @@ Config::load('storage-logos', __DIR__ . '/config/storage/logos.php');
Config::load('storage-mimes', __DIR__ . '/config/storage/mimes.php');
Config::load('storage-inputs', __DIR__ . '/config/storage/inputs.php');
Config::load('storage-outputs', __DIR__ . '/config/storage/outputs.php');
Config::load('runtime-specifications', __DIR__ . '/config/runtimes/specifications.php');
Config::load('framework-specifications', __DIR__ . '/config/frameworks/specifications.php');
Config::load('specifications', __DIR__ . '/config/specifications.php');
Config::load('function-templates', __DIR__ . '/config/function-templates.php');
Config::load('site-templates', __DIR__ . '/config/site-templates.php');

View file

@ -1,16 +0,0 @@
<?php
namespace Appwrite\Functions;
class Specification
{
public const S_05VCPU_512MB = 's-0.5vcpu-512mb';
public const S_1VCPU_512MB = 's-1vcpu-512mb';
public const S_1VCPU_1GB = 's-1vcpu-1gb';
public const S_2VCPU_2GB = 's-2vcpu-2gb';
public const S_2VCPU_4GB = 's-2vcpu-4gb';
public const S_4VCPU_4GB = 's-4vcpu-4gb';
public const S_4VCPU_8GB = 's-4vcpu-8gb';
public const S_8VCPU_4GB = 's-8vcpu-4gb';
public const S_8VCPU_8GB = 's-8vcpu-8gb';
}

View file

@ -1,6 +1,6 @@
<?php
namespace Appwrite\Sites;
namespace Appwrite\Platform\Modules\Compute;
class Specification
{

View file

@ -1,10 +1,10 @@
<?php
namespace Appwrite\Functions\Validator;
namespace Appwrite\Platform\Modules\Compute\Validator;
use Utopia\Validator;
class RuntimeSpecification extends Validator
class Specification extends Validator
{
private array $plan;
@ -35,8 +35,8 @@ class RuntimeSpecification extends Validator
foreach ($this->specifications as $size => $values) {
if ($values['cpus'] <= $this->maxCpus && $values['memory'] <= $this->maxMemory) {
if (!empty($this->plan) && array_key_exists('runtimeSpecifications', $this->plan)) {
if (!\in_array($size, $this->plan['runtimeSpecifications'])) {
if (!empty($this->plan) && array_key_exists('specifications', $this->plan)) {
if (!\in_array($size, $this->plan['specifications'])) {
continue;
}
}

View file

@ -140,7 +140,7 @@ class Create extends Base
$version = $function->getAttribute('version', 'v2');
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
$spec = Config::getParam('runtime-specifications')[$function->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
$spec = Config::getParam('specifications')[$function->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
$runtime = (isset($runtimes[$function->getAttribute('runtime', '')])) ? $runtimes[$function->getAttribute('runtime', '')] : null;

View file

@ -5,8 +5,8 @@ namespace Appwrite\Platform\Modules\Functions\Http\Functions;
use Appwrite\Event\Event;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Extend\Exception;
use Appwrite\Functions\Validator\RuntimeSpecification;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Validator\Specification;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@ -85,9 +85,9 @@ 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', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification(
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification(
$plan,
Config::getParam('runtime-specifications', []),
Config::getParam('specifications', []),
App::getEnv('_APP_COMPUTE_CPUS', APP_COMPUTE_CPUS_DEFAULT),
App::getEnv('_APP_COMPUTE_MEMORY', APP_COMPUTE_MEMORY_DEFAULT)
), 'Runtime specification for the function and builds.', true, ['plan'])

View file

@ -6,8 +6,8 @@ use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Extend\Exception;
use Appwrite\Functions\Validator\RuntimeSpecification;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Validator\Specification;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@ -89,9 +89,9 @@ 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', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification(
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification(
$plan,
Config::getParam('runtime-specifications', []),
Config::getParam('specifications', []),
App::getEnv('_APP_COMPUTE_CPUS', APP_COMPUTE_CPUS_DEFAULT),
App::getEnv('_APP_COMPUTE_MEMORY', APP_COMPUTE_MEMORY_DEFAULT)
), 'Runtime specification for the function and builds.', true, ['plan'])
@ -206,8 +206,6 @@ class Update extends Base
$live = false;
}
$spec = Config::getParam('runtime-specifications')[$specification] ?? [];
// Enforce Cold Start if spec limits change.
if ($function->getAttribute('specification') !== $specification && !empty($function->getAttribute('deployment'))) {
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));

View file

@ -19,7 +19,7 @@ class XList extends Base
public static function getName()
{
return 'listFunctionsSpecifications';
return 'listSpecifications';
}
public function __construct()
@ -28,7 +28,7 @@ class XList extends Base
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/functions/specifications')
->groups(['api', 'functions'])
->desc('List available function runtime specifications')
->desc('List specifications')
->label('scope', 'functions.read')
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
->label('sdk', new Method(
@ -52,25 +52,25 @@ class XList extends Base
public function action(Response $response, array $plan)
{
$allRuntimeSpecs = Config::getParam('runtime-specifications', []);
$allSpecs = Config::getParam('specifications', []);
$runtimeSpecs = [];
foreach ($allRuntimeSpecs as $spec) {
$specs = [];
foreach ($allSpecs as $spec) {
$spec['enabled'] = true;
if (array_key_exists('runtimeSpecifications', $plan)) {
$spec['enabled'] = in_array($spec['slug'], $plan['runtimeSpecifications']);
if (array_key_exists('specifications', $plan)) {
$spec['enabled'] = in_array($spec['slug'], $plan['specifications']);
}
// Only add specs that are within the limits set by environment variables
if ($spec['cpus'] <= System::getEnv('_APP_COMPUTE_CPUS', 1) && $spec['memory'] <= System::getEnv('_APP_COMPUTE_MEMORY', 512)) {
$runtimeSpecs[] = $spec;
$specs[] = $spec;
}
}
$response->dynamic(new Document([
'specifications' => $runtimeSpecs,
'total' => count($runtimeSpecs)
'specifications' => $specs,
'total' => count($specs)
]), Response::MODEL_SPECIFICATION_LIST);
}
}

View file

@ -172,7 +172,7 @@ class Builds extends Action
$version = $this->getVersion($resource);
$runtime = $this->getRuntime($resource, $version);
$spec = Config::getParam('runtime-specifications')[$resource->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
$spec = Config::getParam('specifications')[$resource->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
if ($resource->getCollection() === 'functions' && \is_null($runtime)) {
throw new \Exception('Runtime "' . $resource->getAttribute('runtime', '') . '" is not supported');

View file

@ -5,10 +5,10 @@ namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Validator\Specification;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Sites\Validator\FrameworkSpecification;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Response;
use Utopia\App;
@ -77,9 +77,9 @@ class Create extends Base
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the site.', true)
->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('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new FrameworkSpecification(
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification(
$plan,
Config::getParam('framework-specifications', []),
Config::getParam('specifications', []),
App::getEnv('_APP_COMPUTE_CPUS', APP_COMPUTE_CPUS_DEFAULT),
App::getEnv('_APP_COMPUTE_MEMORY', APP_COMPUTE_MEMORY_DEFAULT)
), 'Framework specification for the site and builds.', true, ['plan'])

View file

@ -6,10 +6,10 @@ use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Platform\Modules\Compute\Validator\Specification;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Sites\Validator\FrameworkSpecification;
use Appwrite\Utopia\Response;
use Executor\Executor;
use Utopia\App;
@ -81,9 +81,9 @@ class Update extends Base
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the site.', true)
->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('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new FrameworkSpecification(
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification(
$plan,
Config::getParam('framework-specifications', []),
Config::getParam('specifications', []),
App::getEnv('_APP_COMPUTE_CPUS', APP_COMPUTE_CPUS_DEFAULT),
App::getEnv('_APP_COMPUTE_MEMORY', APP_COMPUTE_MEMORY_DEFAULT)
), 'Framework specification for the site and builds.', true, ['plan'])
@ -204,8 +204,6 @@ class Update extends Base
$live = false;
}
$spec = Config::getParam('framework-specifications')[$specification] ?? [];
// Enforce Cold Start if spec limits change.
if ($site->getAttribute('specification') !== $specification && !empty($site->getAttribute('deploymentId'))) {
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));

View file

@ -0,0 +1,76 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Specifications;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
class XList extends Base
{
use HTTP;
public static function getName()
{
return 'listSpecifications';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/sites/specifications')
->groups(['api', 'sites'])
->desc('List specifications')
->label('scope', 'sites.read')
->label('resourceType', RESOURCE_TYPE_SITES)
->label('sdk', new Method(
namespace: 'sites',
name: 'listSpecifications',
description: <<<EOT
List allowed site specifications for this instance.
EOT,
auth: [AuthType::KEY, AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_SPECIFICATION_LIST,
)
]
))
->inject('response')
->inject('plan')
->callback([$this, 'action']);
}
public function action(Response $response, array $plan)
{
$allSpecs = Config::getParam('specifications', []);
$specs = [];
foreach ($allSpecs as $spec) {
$spec['enabled'] = true;
if (array_key_exists('specifications', $plan)) {
$spec['enabled'] = in_array($spec['slug'], $plan['specifications']);
}
// Only add specs that are within the limits set by environment variables
if ($spec['cpus'] <= System::getEnv('_APP_COMPUTE_CPUS', 1) && $spec['memory'] <= System::getEnv('_APP_COMPUTE_MEMORY', 512)) {
$specs[] = $spec;
}
}
$response->dynamic(new Document([
'specifications' => $specs,
'total' => count($specs)
]), Response::MODEL_SPECIFICATION_LIST);
}
}

View file

@ -21,6 +21,7 @@ use Appwrite\Platform\Modules\Sites\Http\Sites\Deployment\Update as UpdateSiteDe
use Appwrite\Platform\Modules\Sites\Http\Sites\Get as GetSite;
use Appwrite\Platform\Modules\Sites\Http\Sites\Update as UpdateSite;
use Appwrite\Platform\Modules\Sites\Http\Sites\XList as ListSites;
use Appwrite\Platform\Modules\Sites\Http\Specifications\XList as ListSpecifications;
use Appwrite\Platform\Modules\Sites\Http\Templates\Get as GetTemplate;
use Appwrite\Platform\Modules\Sites\Http\Templates\XList as ListTemplates;
use Appwrite\Platform\Modules\Sites\Http\Usage\Get as GetUsage;
@ -79,5 +80,7 @@ class Http extends Service
// Usage
$this->addAction(ListUsage::getName(), new ListUsage());
$this->addAction(GetUsage::getName(), new GetUsage());
$this->addAction(ListSpecifications::getName(), new ListSpecifications());
}
}

View file

@ -327,7 +327,7 @@ class Functions extends Action
$user ??= new Document();
$functionId = $function->getId();
$deploymentId = $function->getAttribute('deployment', '');
$spec = Config::getParam('runtime-specifications')[$function->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
$spec = Config::getParam('specifications')[$function->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
$log->addTag('deploymentId', $deploymentId);

View file

@ -1,112 +0,0 @@
<?php
namespace Appwrite\Sites\Validator;
use Utopia\Validator;
class FrameworkSpecification extends Validator
{
private array $plan;
private array $specifications;
private float $maxCpus;
private int $maxMemory;
public function __construct(array $plan, array $specifications, float $maxCpus, int $maxMemory)
{
$this->plan = $plan;
$this->specifications = $specifications;
$this->maxCpus = $maxCpus;
$this->maxMemory = $maxMemory;
}
/**
* Get Allowed Specifications.
*
* Get allowed specifications taking into account the limits set by the environment variables and the plan.
*
* @return array
*/
public function getAllowedSpecifications(): array
{
$allowedSpecifications = [];
foreach ($this->specifications as $size => $values) {
if ($values['cpus'] <= $this->maxCpus && $values['memory'] <= $this->maxMemory) {
if (!empty($this->plan) && array_key_exists('frameworkSpecifications', $this->plan)) {
if (!\in_array($size, $this->plan['frameworkSpecifications'])) {
continue;
}
}
$allowedSpecifications[] = $size;
}
}
return $allowedSpecifications;
}
/**
* Get Description.
*
* Returns validator description.
*
* @return string
*/
public function getDescription(): string
{
return 'Specification must be one of: ' . implode(', ', $this->getAllowedSpecifications());
}
/**
* Is valid.
*
* Returns true if valid or false if not.
*
* @param mixed $value
*
* @return bool
*/
public function isValid($value): bool
{
if (empty($value)) {
return false;
}
if (!\is_string($value)) {
return false;
}
if (!\in_array($value, $this->getAllowedSpecifications())) {
return false;
}
return true;
}
/**
* Is array.
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
{
return false;
}
/**
* Get Type.
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_STRING;
}
}

View file

@ -2,7 +2,7 @@
namespace Tests\E2E\General;
use Appwrite\Functions\Specification;
use Appwrite\Platform\Modules\Compute\Specification;
use Appwrite\Tests\Retry;
use CURLFile;
use DateTime;

View file

@ -389,4 +389,13 @@ trait FunctionsBase
return $deployment;
}
protected function listSpecifications(): mixed
{
$specifications = $this->client->call(Client::METHOD_GET, '/functions/specifications', array_merge([
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
return $specifications;
}
}

View file

@ -2,7 +2,7 @@
namespace Tests\E2E\Services\Functions;
use Appwrite\Functions\Specification;
use Appwrite\Platform\Modules\Compute\Specification;
use Appwrite\Tests\Retry;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
@ -22,6 +22,41 @@ class FunctionsCustomServerTest extends Scope
use ProjectCustom;
use SideServer;
public function testListSpecs(): void
{
$specifications = $this->listSpecifications();
$this->assertEquals(200, $specifications['headers']['status-code']);
$this->assertGreaterThan(0, $specifications['body']['total']);
$this->assertArrayHasKey(0, $specifications['body']['specifications']);
$this->assertArrayHasKey('memory', $specifications['body']['specifications'][0]);
$this->assertArrayHasKey('cpus', $specifications['body']['specifications'][0]);
$this->assertArrayHasKey('enabled', $specifications['body']['specifications'][0]);
$this->assertArrayHasKey('slug', $specifications['body']['specifications'][0]);
$function = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Specs function',
'runtime' => 'php-8.0',
'specification' => $specifications['body']['specifications'][0]['slug']
]);
$this->assertEquals(201, $function['headers']['status-code']);
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $function['body']['specification']);
$function = $this->getFunction($function['body']['$id']);
$this->assertEquals(200, $function['headers']['status-code']);
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $function['body']['specification']);
$this->cleanupFunction($function['body']['$id']);
$function = $this->createFunction([
'functionId' => ID::unique(),
'name' => 'Specs function',
'runtime' => 'php-8.0',
'specification' => 'cheap-please'
]);
$this->assertEquals(400, $function['headers']['status-code']);
}
public function testCreateFunction(): array
{
/**
@ -2006,14 +2041,14 @@ class FunctionsCustomServerTest extends Scope
$this->assertEquals(201, $execution['headers']['status-code']);
$this->assertStringContainsString('APPWRITE_FUNCTION_ID', $execution['body']['responseBody']);
$site = $this->updateFunction($functionId, [
$function = $this->updateFunction($functionId, [
'runtime' => 'node-18.0',
'name' => 'Duplicate Deployment Test',
'entrypoint' => 'index.js',
'commands' => 'rm index.js && mv maintenance.js index.js'
]);
$this->assertEquals(200, $site['headers']['status-code']);
$this->assertStringContainsString('maintenance.js', $site['body']['commands']);
$this->assertEquals(200, $function['headers']['status-code']);
$this->assertStringContainsString('maintenance.js', $function['body']['commands']);
$deploymentId2 = $this->setupDuplicateDeployment($functionId, $deploymentId1);
$this->assertNotEmpty($deploymentId2);

View file

@ -414,4 +414,13 @@ trait SitesBase
return $deployment;
}
protected function listSpecifications(): mixed
{
$specifications = $this->client->call(Client::METHOD_GET, '/sites/specifications', array_merge([
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
return $specifications;
}
}

View file

@ -2,7 +2,7 @@
namespace Tests\E2E\Services\Sites;
use Appwrite\Sites\Specification;
use Appwrite\Platform\Modules\Compute\Specification;
use Appwrite\Tests\Retry;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectCustom;
@ -20,6 +20,43 @@ class SitesCustomServerTest extends Scope
use ProjectCustom;
use SideServer;
public function testListSpecs(): void
{
$specifications = $this->listSpecifications();
$this->assertEquals(200, $specifications['headers']['status-code']);
$this->assertGreaterThan(0, $specifications['body']['total']);
$this->assertArrayHasKey(0, $specifications['body']['specifications']);
$this->assertArrayHasKey('memory', $specifications['body']['specifications'][0]);
$this->assertArrayHasKey('cpus', $specifications['body']['specifications'][0]);
$this->assertArrayHasKey('enabled', $specifications['body']['specifications'][0]);
$this->assertArrayHasKey('slug', $specifications['body']['specifications'][0]);
$site = $this->createSite([
'buildRuntime' => 'node-22',
'framework' => 'other',
'name' => 'Specs site',
'siteId' => ID::unique(),
'specification' => $specifications['body']['specifications'][0]['slug']
]);
$this->assertEquals(201, $site['headers']['status-code']);
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $site['body']['specification']);
$site = $this->getSite($site['body']['$id']);
$this->assertEquals(200, $site['headers']['status-code']);
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $site['body']['specification']);
$this->cleanupSite($site['body']['$id']);
$site = $this->createSite([
'buildRuntime' => 'node-22',
'framework' => 'other',
'name' => 'Specs site',
'siteId' => ID::unique(),
'specification' => 'cheap-please'
]);
$this->assertEquals(400, $site['headers']['status-code']);
}
public function testCreateSite(): void
{
/**