Merge pull request #9461 from appwrite/chore-rules-rehaul

Chore: 2nd rules rehaul
This commit is contained in:
Matej Bačo 2025-03-08 21:52:47 +01:00 committed by GitHub
commit 0fa5460a7e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 497 additions and 141 deletions

View file

@ -1024,13 +1024,10 @@ return [
'filters' => [],
],
[
// If 'api', then (empty)
// If 'redirect', then URL
// If 'deployment', then deployment ID
'$id' => ID::custom('value'),
'$id' => ID::custom('trigger'), // 'manual', 'deployment', '' (empty)
'type' => Database::VAR_STRING,
'format' => '',
'size' => 512,
'size' => 32,
'signed' => true,
'required' => false,
'default' => '',
@ -1038,8 +1035,84 @@ return [
'filters' => [],
],
[
// Examples: branch=main
'$id' => ID::custom('automation'),
'$id' => ID::custom('redirectUrl'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 2048,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('redirectStatusCode'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentResourceType'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 32,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentResourceId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentResourceInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentVcsProviderBranch'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@ -1120,16 +1193,51 @@ return [
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_value',
'$id' => '_key_trigger',
'type' => Database::INDEX_KEY,
'attributes' => ['value'],
'lengths' => [512],
'attributes' => ['trigger'],
'lengths' => [32],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_automation',
'$id' => '_key_deploymentResourceType',
'type' => Database::INDEX_KEY,
'attributes' => ['automation'],
'attributes' => ['deploymentResourceType'],
'lengths' => [32],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_deploymentResourceId',
'type' => Database::INDEX_KEY,
'attributes' => ['deploymentResourceId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_deploymentResourceInternalId',
'type' => Database::INDEX_KEY,
'attributes' => ['deploymentResourceInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_deploymentId',
'type' => Database::INDEX_KEY,
'attributes' => ['deploymentId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_deploymentInternalId',
'type' => Database::INDEX_KEY,
'attributes' => ['deploymentInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_deploymentVcsProviderBranch',
'type' => Database::INDEX_KEY,
'attributes' => ['deploymentVcsProviderBranch'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],

View file

@ -267,7 +267,13 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'type' => 'deployment',
'value' => $deployment->getId(),
'trigger' => 'deployment',
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentResourceType' => 'site',
'deploymentResourceId' => $resourceId,
'deploymentResourceInternalId' => $resourceInternalId,
'deploymentVcsProviderBranch' => $providerBranch,
'status' => 'verified',
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),
@ -286,8 +292,13 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'type' => 'deployment',
'value' => $deployment->getId(),
'automation' => 'branch=' . $providerBranch,
'trigger' => 'deployment',
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentResourceType' => 'site',
'deploymentResourceId' => $resourceId,
'deploymentResourceInternalId' => $resourceInternalId,
'deploymentVcsProviderBranch' => $providerBranch,
'status' => 'verified',
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),
@ -310,8 +321,13 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'type' => 'deployment',
'value' => $deployment->getId(),
'automation' => 'commit=' . $providerCommitHash,
'trigger' => 'deployment',
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentResourceType' => 'site',
'deploymentResourceId' => $resourceId,
'deploymentResourceInternalId' => $resourceInternalId,
'deploymentVcsProviderBranch' => $providerBranch,
'status' => 'verified',
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),

View file

@ -136,7 +136,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
/** @var Database $dbForProject */
$dbForProject = $getProjectDB($project);
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('value')));
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('deploymentId')));
if ($deployment->getAttribute('resourceType', '') === 'functions') {
$type = 'function';
@ -148,7 +148,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
Authorization::skip(fn () => $dbForProject->getDocument('functions', $deployment->getAttribute('resourceId', ''))) :
Authorization::skip(fn () => $dbForProject->getDocument('sites', $deployment->getAttribute('resourceId', '')));
$isPreview = $type === 'function' ? false : (!\str_starts_with($rule->getAttribute('automation', ''), 'site='));
$isPreview = $type === 'function' ? false : ($rule->getAttribute('deploymentId', '') !== $resource->getAttribute('deploymentId', ''));
$path = ($swooleRequest->server['request_uri'] ?? '/');
$query = ($swooleRequest->server['query_string'] ?? '');
@ -593,15 +593,14 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$utopia->getRoute()?->label('error', '');
return false;
} elseif ($type === 'redirect') {
$path = ($swooleRequest->server['request_uri'] ?? '/');
$url = $rule->getAttribute('redirectUrl', '');
$query = ($swooleRequest->server['query_string'] ?? '');
if (!empty($query)) {
$path .= '?' . $query;
$url .= '?' . $query;
}
$url = 'https://' . $rule->getAttribute('value', '') . $path;
$response->redirect($url);
$response->redirect($url, \intval($rule->getAttribute('redirectStatusCode', 301)));
return true;
} else {
throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Unknown resource type ' . $type);

View file

@ -182,15 +182,24 @@ class Base extends Action
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$domain = ID::unique() . "." . $sitesDomain;
$ruleId = md5($domain);
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique();
Authorization::skip(
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'trigger' => 'deployment',
'type' => 'deployment',
'value' => $deployment->getId(),
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentResourceType' => 'site',
'deploymentResourceId' => $site->getId(),
'deploymentResourceInternalId' => $site->getInternalId(),
'deploymentVcsProviderBranch' => $providerBranch,
'status' => 'verified',
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),

View file

@ -81,6 +81,8 @@ class Update extends Base
throw new Exception(Exception::BUILD_NOT_READY);
}
$oldDeploymentInternalId = $function->getAttribute('deploymentInternalId', '');
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
'deploymentInternalId' => $deployment->getInternalId(),
'deployment' => $deployment->getId(),
@ -94,10 +96,22 @@ class Update extends Base
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
$this->listRules($project, [
Query::equal("automation", ["function=" . $function->getId()]),
], $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) {
$rule = $rule->setAttribute('value', $deployment->getId());
$queries = [
Query::equal("type", ["deployment"]),
Query::equal("deploymentResourceType", ["function"]),
Query::equal("deploymentResourceInternalId", [$function->getInternalId()]),
];
if (empty($oldDeploymentInternalId)) {
$queries[] = Query::equal("deploymentInternalId", [""]);
} else {
$queries[] = Query::equal("deploymentInternalId", [$oldDeploymentInternalId]);
}
$this->listRules($project, $queries, $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) {
$rule = $rule
->setAttribute('deploymentId', $deployment->getId())
->setAttribute('deploymentInternalId', $deployment->getInternalId());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
});

View file

@ -21,6 +21,7 @@ use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Conflict;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Exception\Restricted;
use Utopia\Database\Exception\Structure;
use Utopia\Database\Helpers\ID;
@ -743,7 +744,7 @@ class Builds extends Action
$rule = Authorization::skip(fn () => $dbForPlatform->findOne('rules', [
Query::equal("projectInternalId", [$project->getInternalId()]),
Query::equal("type", ["deployment"]),
Query::equal("value", [$deployment->getId()])
Query::equal('deploymentInternalId', [$deployment->getInternalId()]),
]));
if ($rule->isEmpty()) {
@ -845,56 +846,117 @@ class Builds extends Action
/** Set auto deploy */
if ($deployment->getAttribute('activate') === true) {
$resource->setAttribute('deploymentInternalId', $deployment->getInternalId());
$resource->setAttribute('live', true);
switch ($resource->getCollection()) {
case 'functions':
$oldDeploymentInternalId = $resource->getAttribute('deploymentInternalId', '');
$resource->setAttribute('deployment', $deployment->getId());
$resource->setAttribute('deploymentInternalId', $deployment->getInternalId());
$resource = $dbForProject->updateDocument('functions', $resource->getId(), $resource);
$this->listRules($project, [
Query::equal("automation", ["function=" . $resource->getId()]),
], $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) {
$rule = $rule->setAttribute('value', $deployment->getId());
$queries = [
Query::equal("projectInternalId", [$project->getInternalId()]),
Query::equal("type", ["deployment"]),
Query::equal("deploymentResourceInternalId", [$resource->getInternalId()]),
Query::equal('deploymentResourceType', ['function']),
Query::equal('trigger', ['manual']),
];
if (empty($oldDeploymentInternalId)) {
$queries[] = Query::equal("deploymentInternalId", [""]);
} else {
$queries[] = Query::equal("deploymentInternalId", [$oldDeploymentInternalId]);
}
$this->listRules($project, $queries, $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) {
$rule = $rule
->setAttribute('deploymentId', $deployment->getId())
->setAttribute('deploymentInternalId', $deployment->getInternalId());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
});
break;
case 'sites':
$oldDeploymentInternalId = $resource->getAttribute('deploymentInternalId', '');
$resource->setAttribute('deploymentId', $deployment->getId());
$resource->setAttribute('deploymentInternalId', $deployment->getInternalId());
$resource = $dbForProject->updateDocument('sites', $resource->getId(), $resource);
$this->listRules($project, [
Query::equal("automation", ["site=" . $resource->getId()]),
], $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) {
$rule = $rule->setAttribute('value', $deployment->getId());
$queries = [
Query::equal("projectInternalId", [$project->getInternalId()]),
Query::equal("type", ["deployment"]),
Query::equal("deploymentResourceInternalId", [$resource->getInternalId()]),
Query::equal('deploymentResourceType', ['site']),
Query::equal('trigger', ['manual']),
];
if (empty($oldDeploymentInternalId)) {
$queries[] = Query::equal("deploymentInternalId", [""]);
} else {
$queries[] = Query::equal("deploymentInternalId", [$oldDeploymentInternalId]);
}
$this->listRules($project, $queries, $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) {
$rule = $rule
->setAttribute('deploymentId', $deployment->getId())
->setAttribute('deploymentInternalId', $deployment->getInternalId());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
});
// VCS branch
$branchName = $deployment->getAttribute('providerBranch');
if (!empty($branchName)) {
$this->listRules($project, [
Query::equal("automation", ["branch=" . $branchName]),
], $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) {
$rule = $rule->setAttribute('value', $deployment->getId());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
});
}
// VCS commit
$commitHash = $deployment->getAttribute('providerCommitHash', '');
if (!empty($commitHash)) {
$this->listRules($project, [
Query::equal("automation", ["commit=" . $commitHash]),
], $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) {
$rule = $rule->setAttribute('value', $deployment->getId());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
});
}
break;
}
}
if ($resource->getCollection() === 'sites') {
// VCS branch
$branchName = $deployment->getAttribute('providerBranch');
if (!empty($branchName)) {
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$domain = "branch-{$branchName}-{$resource->getId()}-{$project->getId()}.{$sitesDomain}";
$ruleId = md5($domain);
try {
$dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'type' => 'deployment',
'trigger' => 'deployment',
'deploymentId' => $deployment->getId(),
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentResourceType' => 'site',
'deploymentResourceId' => $deployment->getId(),
'deploymentResourceInternalId' => $deployment->getInternalId(),
'deploymentVcsProviderBranch' => $branchName,
'status' => 'verified',
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),
]));
} catch (Duplicate $err) {
$rule = $dbForPlatform->getDocument('rules', $ruleId);
$rule = $rule
->setAttribute('deploymentId', $deployment->getId())
->setAttribute('deploymentInternalId', $deployment->getInternalId());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
}
$this->listRules($project, [
Query::equal("projectInternalId", [$project->getInternalId()]),
Query::equal("type", ["deployment"]),
Query::equal("deploymentResourceInternalId", [$resource->getInternalId()]),
Query::equal('deploymentResourceType', ['site']),
Query::equal("deploymentVcsProviderBranch", [$branchName]),
Query::equal("trigger", ['manual']),
], $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) {
$rule = $rule
->setAttribute('deploymentId', $deployment->getId())
->setAttribute('deploymentInternalId', $deployment->getInternalId());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
});
}
}
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
Console::info('Build has been canceled');
@ -1155,7 +1217,7 @@ class Builds extends Action
$rule = Authorization::skip(fn () => $dbForPlatform->findOne('rules', [
Query::equal("projectInternalId", [$project->getInternalId()]),
Query::equal("type", ["deployment"]),
Query::equal("value", [$deployment->getId()])
Query::equal("deploymentInternalId", [$deployment->getInternalId()]),
]));
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';

View file

@ -124,7 +124,7 @@ class Create extends Action
'domain' => $domain->get(),
'status' => $status,
'type' => 'api',
'value' => '',
'trigger' => 'manual',
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain->get()]),
]);

View file

@ -108,6 +108,8 @@ class Create extends Action
throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND);
}
$deployment = $dbForProject->getDocument('deployments', $function->getAttribute('deployment', ''));
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
@ -130,10 +132,14 @@ class Create extends Action
'domain' => $domain->get(),
'status' => $status,
'type' => 'deployment',
'value' => $function->getAttribute('deployment', ''),
'trigger' => 'manual',
'deploymentId' => $deployment->isEmpty() ? '' : $deployment->getId(),
'deploymentInternalId' => $deployment->isEmpty() ? '' : $deployment->getInternalId(),
'deploymentResourceType' => 'function',
'deploymentResourceId' => $function->getId(),
'deploymentResourceInternalId' => $function->getInternalId(),
'deploymentVcsProviderBranch' => $branch,
'certificateId' => '',
'automation' => 'function=' . $function->getId(),
'automation' => !empty($branch) ? ('branch=' . $branch) : ('function=' . $function->getId()),
'search' => implode(' ', [$ruleId, $domain->get(), $branch]),
]);

View file

@ -20,6 +20,8 @@ use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
use Utopia\Validator\Domain as ValidatorDomain;
use Utopia\Validator\URL;
use Utopia\Validator\WhiteList;
class Create extends Action
{
@ -59,7 +61,8 @@ class Create extends Action
->label('abuse-key', 'userId:{userId}, url:{url}')
->label('abuse-time', 60)
->param('domain', null, new ValidatorDomain(), 'Domain name.')
->param('target', null, new ValidatorDomain(), 'Target domain (hostname) of redirection')
->param('url', null, new URL(), 'Target URL of redirection')
->param('statusCode', null, new WhiteList([301, 302, 307, 308]), 'Status code of redirection')
->inject('response')
->inject('project')
->inject('queueForCertificates')
@ -68,7 +71,7 @@ class Create extends Action
->callback([$this, 'action']);
}
public function action(string $domain, string $target, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform)
public function action(string $domain, string $url, int $statusCode, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform)
{
$mainDomain = System::getEnv('_APP_DOMAIN', '');
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
@ -92,12 +95,6 @@ class Create extends Action
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.');
}
try {
$target = new Domain($target);
} catch (\Throwable) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Target may not start with http:// or https://.');
}
// Apex domain prevention due to CNAME limitations
if (empty(App::getEnv('_APP_DOMAINS_NAMESERVERS', ''))) {
if ($domain->get() === $domain->getRegisterable()) {
@ -127,7 +124,9 @@ class Create extends Action
'domain' => $domain->get(),
'status' => $status,
'type' => 'redirect',
'value' => $target->get(),
'trigger' => 'manual',
'redirectUrl' => $url,
'redirectStatusCode' => $statusCode,
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain->get()]),
]);

View file

@ -108,6 +108,8 @@ class Create extends Action
throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND);
}
$deployment = $dbForProject->getDocument('deployments', $site->getAttribute('deploymentId', ''));
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
@ -130,9 +132,14 @@ class Create extends Action
'domain' => $domain->get(),
'status' => $status,
'type' => 'deployment',
'value' => $site->getAttribute('deploymentId', ''),
'trigger' => 'manual',
'deploymentId' => $deployment->isEmpty() ? '' : $deployment->getId(),
'deploymentInternalId' => $deployment->isEmpty() ? '' : $deployment->getInternalId(),
'deploymentResourceType' => 'site',
'deploymentResourceId' => $site->getId(),
'deploymentResourceInternalId' => $site->getInternalId(),
'deploymentVcsProviderBranch' => $branch,
'certificateId' => '',
'automation' => !empty($branch) ? ('branch=' . $branch) : ('site=' . $site->getId()),
'search' => implode(' ', [$ruleId, $domain->get(), $branch]),
]);

View file

@ -236,7 +236,10 @@ class Create extends Action
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$domain = ID::unique() . "." . $sitesDomain;
$ruleId = md5($domain);
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique();
Authorization::skip(
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId,
@ -244,7 +247,12 @@ class Create extends Action
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'type' => 'deployment',
'value' => $deployment->getId(),
'trigger' => 'deployment',
'deploymentId' => $deployment->isEmpty() ? '' : $deployment->getId(),
'deploymentInternalId' => $deployment->isEmpty() ? '' : $deployment->getInternalId(),
'deploymentResourceType' => 'site',
'deploymentResourceId' => $site->getId(),
'deploymentResourceInternalId' => $site->getInternalId(),
'status' => 'verified',
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),
@ -293,6 +301,7 @@ class Create extends Action
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'type' => 'deployment',
'trigger' => 'deployment',
'value' => $deployment->getId(),
'status' => 'verified',
'certificateId' => '',

View file

@ -118,7 +118,10 @@ class Create extends Action
// Preview deployments for sites
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$domain = ID::unique() . "." . $sitesDomain;
$ruleId = md5($domain);
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique();
Authorization::skip(
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId,
@ -126,7 +129,12 @@ class Create extends Action
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'type' => 'deployment',
'value' => $deployment->getId(),
'trigger' => 'deployment',
'deploymentId' => $deployment->isEmpty() ? '' : $deployment->getId(),
'deploymentInternalId' => $deployment->isEmpty() ? '' : $deployment->getInternalId(),
'deploymentResourceType' => 'site',
'deploymentResourceId' => $site->getId(),
'deploymentResourceInternalId' => $site->getInternalId(),
'status' => 'verified',
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),

View file

@ -149,7 +149,10 @@ class Create extends Base
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$domain = ID::unique() . "." . $sitesDomain;
$ruleId = md5($domain);
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique();
Authorization::skip(
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId,
@ -157,7 +160,12 @@ class Create extends Base
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'type' => 'deployment',
'value' => $deployment->getId(),
'trigger' => 'deployment',
'deploymentId' => $deployment->isEmpty() ? '' : $deployment->getId(),
'deploymentInternalId' => $deployment->isEmpty() ? '' : $deployment->getInternalId(),
'deploymentResourceType' => 'site',
'deploymentResourceId' => $site->getId(),
'deploymentResourceInternalId' => $site->getInternalId(),
'status' => 'verified',
'certificateId' => '',
'search' => implode(' ', [$ruleId, $domain]),

View file

@ -78,15 +78,29 @@ class Update extends Base
throw new Exception(Exception::BUILD_NOT_READY);
}
$oldDeploymentInternalId = $site->getAttribute('deploymentInternalId', '');
$site = $dbForProject->updateDocument('sites', $site->getId(), new Document(array_merge($site->getArrayCopy(), [
'deploymentInternalId' => $deployment->getInternalId(),
'deploymentId' => $deployment->getId(),
])));
$this->listRules($project, [
Query::equal("automation", ["site=" . $site->getId()]),
], $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) {
$rule = $rule->setAttribute('value', $deployment->getId());
$queries = [
Query::equal("type", ["deployment"]),
Query::equal("deploymentResourceType", ["site"]),
Query::equal("deploymentResourceInternalId", [$site->getInternalId()]),
];
if (empty($oldDeploymentInternalId)) {
$queries[] = Query::equal("deploymentInternalId", [""]);
} else {
$queries[] = Query::equal("deploymentInternalId", [$oldDeploymentInternalId]);
}
$this->listRules($project, $queries, $dbForPlatform, function (Document $rule) use ($dbForPlatform, $deployment) {
$rule = $rule
->setAttribute('deploymentId', $deployment->getId())
->setAttribute('deploymentInternalId', $deployment->getInternalId());
$dbForPlatform->updateDocument('rules', $rule->getId(), $rule);
});

View file

@ -761,7 +761,8 @@ class Deletes extends Action
Console::info("Deleting rules for site " . $siteId);
$this->deleteByGroup('rules', [
Query::equal('type', ['deployment']),
Query::equal('automation', ['site=' . $siteId]),
Query::equal('deploymentResourceType', ['site']),
Query::equal('deploymentResourceInternalId', [$siteInternalId]),
Query::equal('projectInternalId', [$project->getInternalId()])
], $dbForPlatform, function (Document $document) use ($dbForPlatform, $certificates) {
$this->deleteRule($dbForPlatform, $document, $certificates);
@ -793,6 +794,18 @@ class Deletes extends Action
$this->deleteDeploymentRules($dbForPlatform, $document, $project, $certificates);
});
/**
* Delete builds
*/
Console::info("Deleting builds for site " . $siteId);
foreach ($deploymentInternalIds as $deploymentInternalId) {
$this->deleteByGroup('builds', [
Query::equal('deploymentInternalId', [$deploymentInternalId])
], $dbForProject, function (Document $document) use ($deviceForBuilds) {
$this->deleteBuildFiles($deviceForBuilds, $document);
});
}
/**
* Delete rules for all deployments of the site
*/
@ -847,7 +860,8 @@ class Deletes extends Action
Console::info("Deleting rules for function " . $functionId);
$this->deleteByGroup('rules', [
Query::equal('type', ['deployment']),
Query::equal('automation', ['function=' . $functionId]),
Query::equal('deploymentResourceType', ['site']),
Query::equal('deploymentResourceInternalId', [$functionInternalId]),
Query::equal('projectInternalId', [$project->getInternalId()])
], $dbForPlatform, function (Document $document) use ($project, $dbForPlatform, $certificates) {
$this->deleteRule($dbForPlatform, $document, $certificates);
@ -914,7 +928,7 @@ class Deletes extends Action
Console::info("Deleting rules for site " . $deployment->getId());
$this->deleteByGroup('rules', [
Query::equal('type', ['deployment']),
Query::equal('value', [$deployment->getId()]),
Query::equal('deploymentInternalId', [$deployment->getInternalId()]),
Query::equal('projectInternalId', [$project->getInternalId()])
], $dbForPlatform, function (Document $document) use ($dbForPlatform, $certificates) {
$this->deleteRule($dbForPlatform, $document, $certificates);
@ -1072,7 +1086,7 @@ class Deletes extends Action
Console::info("Deleting rules for deployment " . $deploymentId);
$this->deleteByGroup('rules', [
Query::equal('type', ['deployment']),
Query::equal('value', [$deploymentId]),
Query::equal('deploymentResourceInternalId', [$deploymentInternalId]),
Query::equal('projectInternalId', [$project->getInternalId()])
], $dbForPlatform, function (Document $document) use ($dbForPlatform, $certificates) {
$this->deleteRule($dbForPlatform, $document, $certificates);

View file

@ -7,9 +7,11 @@ class Rules extends Base
public const ALLOWED_ATTRIBUTES = [
'domain',
'type',
'value',
'automation',
'url'
'trigger',
'deploymentResourceType',
'deploymentResourceId',
'deploymentId',
'deploymentVcsProviderBranch'
];
/**

View file

@ -40,18 +40,47 @@ class Rule extends Model
'default' => '',
'example' => 'deployment',
])
->addRule('value', [
->addRule('trigger', [
'type' => self::TYPE_STRING,
'description' => 'Detail specification for the type. If type is "api", this is empty. If type is "redirect", this is URL. If type is "deployment", this is deployment ID.',
'description' => 'Defines how the rule was created. Possible values are "manual" or "deployment"',
'default' => '',
'example' => '67a9cf1a00150ee93abd',
'example' => 'manual',
])
->addRule('automation', [
->addRule('redirectUrl', [
'type' => self::TYPE_STRING,
'description' => 'Action that results in a rule update. If VCS branch, value can be of syntax "branch=[name]"',
'array' => false,
'description' => 'URL to redirect to. Used if type is "redirect"',
'default' => '',
'example' => 'branch=dev',
'example' => 'https://appwrite.io/docs',
])
->addRule('redirectStatusCode', [
'type' => self::TYPE_INTEGER,
'description' => 'Status code to apply during redirect. Used if type is "redirect"',
'default' => '',
'example' => 301,
])
->addRule('deploymentId', [
'type' => self::TYPE_STRING,
'description' => 'ID of deployment. Used if type is "deployment"',
'default' => '',
'example' => 'n3u9feiwmf',
])
->addRule('deploymentResourceType', [
'type' => self::TYPE_STRING,
'description' => 'Type of deployment. Possible values are "function", "site". Used if rule\'s type is "deployment".',
'default' => '',
'example' => 'function',
])
->addRule('deploymentResourceId', [
'type' => self::TYPE_STRING,
'description' => 'ID deployment\'s resource. Used if type is "deployment"',
'default' => '',
'example' => 'n3u9feiwmf',
])
->addRule('deploymentVcsProviderBranch', [
'type' => self::TYPE_STRING,
'description' => 'Name of Git branch that updates rule. Used if type is "deployment"',
'default' => '',
'example' => 'function',
])
->addRule('status', [
'type' => self::TYPE_STRING,

View file

@ -308,7 +308,8 @@ trait FunctionsBase
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::equal('automation', ['function=' . $functionId])->toString(),
Query::equal('deploymentResourceId', [$functionId])->toString(),
Query::equal('trigger', ['manual'])->toString(),
Query::equal('type', ['deployment'])->toString(),
],
]);

View file

@ -68,14 +68,15 @@ trait ProxyBase
return $rule;
}
protected function createRedirectRule(string $domain, string $target): mixed
protected function createRedirectRule(string $domain, string $url, int $statusCode): mixed
{
$rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/redirect', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'domain' => $domain,
'target' => $target,
'url' => $url,
'statusCode' => $statusCode,
]);
return $rule;
@ -114,9 +115,9 @@ trait ProxyBase
return $rule['body']['$id'];
}
protected function setupRedirectRule(string $domain, string $target): string
protected function setupRedirectRule(string $domain, string $url, int $statusCode): string
{
$rule = $this->createRedirectRule($domain, $target);
$rule = $this->createRedirectRule($domain, $url, $statusCode);
$this->assertEquals(201, $rule['headers']['status-code'], 'Failed to setup rule: ' . \json_encode($rule));

View file

@ -22,11 +22,16 @@ class ProxyCustomServerTest extends Scope
$this->assertEquals(201, $rule['headers']['status-code']);
$this->assertEquals($domain, $rule['body']['domain']);
$this->assertEquals('manual', $rule['body']['trigger']);
$this->assertArrayHasKey('$id', $rule['body']);
$this->assertArrayHasKey('domain', $rule['body']);
$this->assertArrayHasKey('type', $rule['body']);
$this->assertArrayHasKey('value', $rule['body']);
$this->assertArrayHasKey('automation', $rule['body']);
$this->assertArrayHasKey('status', $rule['body']);
$this->assertArrayHasKey('redirectUrl', $rule['body']);
$this->assertArrayHasKey('redirectStatusCode', $rule['body']);
$this->assertArrayHasKey('deploymentResourceType', $rule['body']);
$this->assertArrayHasKey('deploymentId', $rule['body']);
$this->assertArrayHasKey('deploymentResourceId', $rule['body']);
$this->assertArrayHasKey('deploymentVcsProviderBranch', $rule['body']);
$this->assertArrayHasKey('logs', $rule['body']);
$this->assertArrayHasKey('renewAt', $rule['body']);
@ -117,13 +122,33 @@ class ProxyCustomServerTest extends Scope
$response = $proxyClient->call(Client::METHOD_GET, '/todos/1');
$this->assertEquals(404, $response['headers']['status-code']);
$ruleId = $this->setupRedirectRule($domain, 'jsonplaceholder.typicode.com');
$ruleId = $this->setupRedirectRule($domain, 'https://jsonplaceholder.typicode.com/todos/1', 301);
$this->assertNotEmpty($ruleId);
$response = $proxyClient->call(Client::METHOD_GET, '/todos/1');
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(1, $response['body']['id']);
$response = $proxyClient->call(Client::METHOD_GET, '/');
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(1, $response['body']['id']);
$response = $proxyClient->call(Client::METHOD_GET, '/', followRedirects: false);
$this->assertEquals(301, $response['headers']['status-code']);
$this->assertEquals('https://jsonplaceholder.typicode.com/todos/1', $response['headers']['location']);
$domain = \uniqid() . '-redirect-307.custom.localhost';
$ruleId = $this->setupRedirectRule($domain, 'https://jsonplaceholder.typicode.com/todos/1', 307);
$this->assertNotEmpty($ruleId);
$proxyClient = new Client();
$proxyClient->setEndpoint('http://appwrite');
$proxyClient->addHeader('x-appwrite-hostname', $domain);
$response = $proxyClient->call(Client::METHOD_GET, '/', followRedirects: false);
$this->assertEquals(307, $response['headers']['status-code']);
$this->assertEquals('https://jsonplaceholder.typicode.com/todos/1', $response['headers']['location']);
$this->cleanupRule($ruleId);
}
@ -161,7 +186,8 @@ class ProxyCustomServerTest extends Scope
'queries' => [
Query::limit(1)->toString(),
Query::equal('type', ['deployment'])->toString(),
Query::equal('automation', ['function=' . $functionId])->toString()
Query::equal('deploymentResourceType', ['function'])->toString(),
Query::equal('deploymentResourceId', [$functionId])->toString(),
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
@ -172,7 +198,7 @@ class ProxyCustomServerTest extends Scope
'queries' => [
Query::limit(1)->toString(),
Query::equal('type', ['deployment'])->toString(),
Query::equal('value', [$deploymentId])->toString()
Query::equal('deploymentId', [$deploymentId])->toString()
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
@ -206,6 +232,18 @@ class ProxyCustomServerTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertStringContainsString('Contact page', $response['body']);
$rules = $this->listRules([
'queries' => [
Query::limit(1)->toString(),
Query::equal('trigger', ['deployment'])->toString(),
Query::equal('type', ['deployment'])->toString(),
Query::equal('deploymentResourceType', ['site'])->toString(),
Query::equal('deploymentResourceId', [$siteId])->toString(),
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
$this->assertGreaterThan(0, $rules['body']['total']);
$this->cleanupRule($ruleId);
$this->cleanupSite($siteId);
@ -215,7 +253,8 @@ class ProxyCustomServerTest extends Scope
'queries' => [
Query::limit(1)->toString(),
Query::equal('type', ['deployment'])->toString(),
Query::equal('automation', ['site=' . $siteId])->toString()
Query::equal('deploymentResourceType', ['site'])->toString(),
Query::equal('deploymentResourceId', [$siteId])->toString(),
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
@ -226,7 +265,7 @@ class ProxyCustomServerTest extends Scope
'queries' => [
Query::limit(1)->toString(),
Query::equal('type', ['deployment'])->toString(),
Query::equal('value', [$deploymentId])->toString()
Query::equal('deploymentId', [$deploymentId])->toString()
]
]);
$this->assertEquals(200, $rules['headers']['status-code']);
@ -235,7 +274,7 @@ class ProxyCustomServerTest extends Scope
});
}
public function testCreatSiteBranchRule(): void
public function testCreateSiteBranchRule(): void
{
$domain = \uniqid() . '-site-branch.custom.localhost';
@ -251,12 +290,11 @@ class ProxyCustomServerTest extends Scope
$rule = $this->getRule($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->assertEquals('branch=dev', $rule['body']['automation']);
$this->cleanupRule($ruleId);
}
public function testCreatFunctionBranchRule(): void
public function testCreateFunctionBranchRule(): void
{
$domain = \uniqid() . '-function-branch.custom.localhost';
@ -272,9 +310,10 @@ class ProxyCustomServerTest extends Scope
$rule = $this->getRule($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->assertEquals('branch=dev', $rule['body']['automation']);
$this->cleanupRule($ruleId);
$this->cleanupFunction($functionId);
}
public function testUpdateRule(): void
@ -322,11 +361,16 @@ class ProxyCustomServerTest extends Scope
$rule = $this->getRule($ruleId);
$this->assertEquals(200, $rule['headers']['status-code']);
$this->assertEquals($domain, $rule['body']['domain']);
$this->assertEquals('manual', $rule['body']['trigger']);
$this->assertArrayHasKey('$id', $rule['body']);
$this->assertArrayHasKey('domain', $rule['body']);
$this->assertArrayHasKey('type', $rule['body']);
$this->assertArrayHasKey('value', $rule['body']);
$this->assertArrayHasKey('automation', $rule['body']);
$this->assertArrayHasKey('status', $rule['body']);
$this->assertArrayHasKey('redirectUrl', $rule['body']);
$this->assertArrayHasKey('redirectStatusCode', $rule['body']);
$this->assertArrayHasKey('deploymentResourceType', $rule['body']);
$this->assertArrayHasKey('deploymentId', $rule['body']);
$this->assertArrayHasKey('deploymentResourceId', $rule['body']);
$this->assertArrayHasKey('deploymentVcsProviderBranch', $rule['body']);
$this->assertArrayHasKey('logs', $rule['body']);
$this->assertArrayHasKey('renewAt', $rule['body']);
@ -356,11 +400,17 @@ class ProxyCustomServerTest extends Scope
$this->assertEquals(1, $rules['body']['total']);
$this->assertCount(1, $rules['body']['rules']);
$this->assertEquals($rule1Domain, $rules['body']['rules'][0]['domain']);
$this->assertEquals('manual', $rules['body']['rules'][0]['trigger']);
$this->assertArrayHasKey('$id', $rules['body']['rules'][0]);
$this->assertArrayHasKey('domain', $rules['body']['rules'][0]);
$this->assertArrayHasKey('type', $rules['body']['rules'][0]);
$this->assertArrayHasKey('value', $rules['body']['rules'][0]);
$this->assertArrayHasKey('automation', $rules['body']['rules'][0]);
$this->assertArrayHasKey('status', $rules['body']['rules'][0]);
$this->assertArrayHasKey('redirectUrl', $rules['body']['rules'][0]);
$this->assertArrayHasKey('redirectStatusCode', $rules['body']['rules'][0]);
$this->assertArrayHasKey('deploymentResourceType', $rules['body']['rules'][0]);
$this->assertArrayHasKey('deploymentId', $rules['body']['rules'][0]);
$this->assertArrayHasKey('deploymentResourceId', $rules['body']['rules'][0]);
$this->assertArrayHasKey('deploymentVcsProviderBranch', $rules['body']['rules'][0]);
$this->assertArrayHasKey('logs', $rules['body']['rules'][0]);
$this->assertArrayHasKey('renewAt', $rules['body']['rules'][0]);

View file

@ -343,7 +343,8 @@ trait SitesBase
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::equal('automation', ['site=' . $siteId])->toString(),
Query::equal('deploymentResourceId', [$siteId])->toString(),
Query::equal('trigger', ['manual'])->toString(),
Query::equal('type', ['deployment'])->toString(),
],
]);
@ -365,9 +366,9 @@ trait SitesBase
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::equal('value', [$deploymentId])->toString(),
Query::equal('deploymentId', [$deploymentId])->toString(),
Query::equal('type', ['deployment'])->toString(),
Query::equal('automation', [''])->toString(),
Query::equal('trigger', ['deployment'])->toString(),
],
]);

View file

@ -154,7 +154,7 @@ class SitesCustomServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::equal('automation', ['site=' . $siteId])
Query::equal('deploymentResourceId', [$siteId])
]
]);
@ -1653,35 +1653,34 @@ class SitesCustomServerTest extends Scope
$this->assertNotEmpty($siteId);
$domain = $this->setupSiteDomain($siteId);
$deploymentId = $this->setupDeployment($siteId, [
'code' => $this->packageSite('static'),
'activate' => 'true'
]);
$this->assertNotEmpty($deploymentId);
$oldDeploymentDomain = $this->getDeploymentDomain($deploymentId);
$this->assertNotEmpty($oldDeploymentDomain);
$deploymentId = $this->setupDeployment($siteId, [
'code' => $this->packageSite('static'),
'activate' => 'true'
]);
$this->assertNotEmpty($deploymentId);
$domain = $this->getSiteDomain($siteId);
$previewDomain = $this->getDeploymentDomain($deploymentId);
$this->assertNotEmpty($domain);
$this->assertNotEmpty($previewDomain);
$newDeploymentDomain = $this->getDeploymentDomain($deploymentId);
$this->assertNotEmpty($newDeploymentDomain);
$proxyClient = new Client();
$proxyClient->setEndpoint('http://' . $domain);
$proxyClient->setEndpoint('http://' . $newDeploymentDomain);
$response = $proxyClient->call(Client::METHOD_GET, '/');
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertStringContainsString("Hello Appwrite", $response['body']);
$this->assertStringNotContainsString("Preview by", $response['body']);
$contentLength = $response['headers']['content-length'];
$proxyClient = new Client();
$proxyClient->setEndpoint('http://' . $previewDomain);
$proxyClient->setEndpoint('http://' . $oldDeploymentDomain);
$response = $proxyClient->call(Client::METHOD_GET, '/', followRedirects: false);
$this->assertEquals(301, $response['headers']['status-code']);
$this->assertStringContainsString('/console/auth/preview', $response['headers']['location']);