Merge pull request #10959 from appwrite/feat-auto-delete-depoyments

Feat: Auto-delete deployments
This commit is contained in:
Matej Bačo
2025-12-16 13:30:44 +01:00
committed by GitHub
15 changed files with 521 additions and 7 deletions
+22
View File
@@ -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,
@@ -1080,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,
+43
View File
@@ -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;
@@ -198,6 +199,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'])
+1
View File
@@ -85,6 +85,7 @@ const APP_HOSTNAME_INTERNAL = 'appwrite';
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_PLATFORM_SERVER = 'server';
const APP_PLATFORM_CLIENT = 'client';
const APP_PLATFORM_CONSOLE = 'console';
@@ -109,6 +109,7 @@ class Create extends Base
->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')
@@ -148,6 +149,7 @@ class Create extends Base
string $templateOwner,
string $templateRootDirectory,
string $templateVersion,
int $deploymentRetention,
Response $response,
Database $dbForProject,
callable $timelimit,
@@ -218,6 +220,7 @@ class Create extends Base
'logging' => $logging,
'name' => $name,
'runtime' => $runtime,
'deploymentRetention' => $deploymentRetention,
'deploymentInternalId' => '',
'deploymentId' => '',
'events' => $events,
@@ -101,6 +101,7 @@ class Update extends Base
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')
@@ -133,6 +134,7 @@ class Update extends Base
string $providerRootDirectory,
string $buildSpecification,
string $runtimeSpecification,
int $deploymentRetention,
Request $request,
Response $response,
Database $dbForProject,
@@ -222,7 +224,7 @@ class Update extends Base
'resourceId' => $function->getId(),
'resourceInternalId' => $function->getSequence(),
'resourceType' => 'function',
'providerPullRequestIds' => []
'providerPullRequestIds' => [],
]));
$repositoryId = $repository->getId();
@@ -275,6 +277,7 @@ class Update extends Base
'entrypoint' => $entrypoint,
'commands' => $commands,
'scopes' => $scopes,
'deploymentRetention' => $deploymentRetention,
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getSequence(),
'providerRepositoryId' => $providerRepositoryId,
@@ -91,6 +91,7 @@ class Create extends Base
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')
@@ -120,6 +121,7 @@ class Create extends Base
string $providerRootDirectory,
string $buildSpecification,
string $runtimeSpecification,
int $deploymentRetention,
Response $response,
Database $dbForProject,
Document $project,
@@ -154,6 +156,7 @@ class Create extends Base
'logging' => $logging,
'name' => $name,
'framework' => $framework,
'deploymentRetention' => $deploymentRetention,
'deploymentInternalId' => '',
'deploymentId' => '',
'timeout' => $timeout,
@@ -95,6 +95,7 @@ class Update extends Base
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')
@@ -128,6 +129,7 @@ class Update extends Base
string $providerRootDirectory,
string $buildSpecification,
string $runtimeSpecification,
int $deploymentRetention,
Request $request,
Response $response,
Database $dbForProject,
@@ -270,6 +272,7 @@ class Update extends Base
'logging' => $logging,
'live' => $live,
'timeout' => $timeout,
'deploymentRetention' => $deploymentRetention,
'installCommand' => $installCommand,
'buildCommand' => $buildCommand,
'startCommand' => $startCommand,
+15 -5
View File
@@ -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
+60 -1
View File
@@ -6,6 +6,7 @@ use Appwrite\Auth\Auth;
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')
->callback($this->action(...));
}
@@ -85,7 +87,8 @@ class Deletes extends Action
Executor $executor,
string $executionRetention,
string $auditRetention,
Log $log
Log $log,
DeleteEvent $queueForDeletes,
): void {
$payload = $message->getPayload() ?? [];
@@ -189,6 +192,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));
@@ -308,6 +312,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
@@ -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.',
@@ -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.',
@@ -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);
}
}
@@ -2629,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
@@ -139,4 +140,58 @@ class SitesConsoleClientTest extends Scope
$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);
}
}
@@ -3076,4 +3076,134 @@ class SitesCustomServerTest extends Scope
$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);
}
}
}