mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge pull request #10959 from appwrite/feat-auto-delete-depoyments
Feat: Auto-delete deployments
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,
|
||||
@@ -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,
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user