Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions src/Migration/Destinations/Appwrite.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
use Appwrite\Enums\PasswordHash;
use Appwrite\Enums\ProjectProtocolId;
use Appwrite\Enums\ProjectServiceId;
use Appwrite\Enums\ProxyResourceType;
use Appwrite\Enums\Runtime;
use Appwrite\Enums\SmtpEncryption;
use Appwrite\Enums\StatusCode;
use Appwrite\InputFile;
use Appwrite\Services\Functions;
use Appwrite\Services\Messaging;
use Appwrite\Services\Project;
use Appwrite\Services\Proxy;
use Appwrite\Services\Sites;
use Appwrite\Services\Storage;
use Appwrite\Services\Teams;
Expand Down Expand Up @@ -52,6 +55,7 @@
use Utopia\Migration\Resources\Database\Index;
use Utopia\Migration\Resources\Database\Row;
use Utopia\Migration\Resources\Database\Table;
use Utopia\Migration\Resources\Domains\Rule;
use Utopia\Migration\Resources\Functions\Deployment;
use Utopia\Migration\Resources\Functions\EnvVar;
use Utopia\Migration\Resources\Functions\Func;
Expand Down Expand Up @@ -107,6 +111,7 @@ class Appwrite extends Destination
private Functions $functions;
private Messaging $messaging;
private Project $project;
private Proxy $proxy;
private Sites $sites;
private Storage $storage;
private Teams $teams;
Expand Down Expand Up @@ -192,6 +197,7 @@ public function __construct(
$this->functions = new Functions($this->client);
$this->messaging = new Messaging($this->client);
$this->project = new Project($this->client);
$this->proxy = new Proxy($this->client);
$this->sites = new Sites($this->client);
$this->storage = new Storage($this->client);
$this->teams = new Teams($this->client);
Expand Down Expand Up @@ -303,6 +309,9 @@ public static function getSupportedResources(): array

// Backups
Resource::TYPE_BACKUP_POLICY,

// Domains
Resource::TYPE_RULE,
];
}

Expand Down Expand Up @@ -462,6 +471,7 @@ protected function import(array $resources, callable $callback): void
Transfer::GROUP_INTEGRATIONS => $this->importIntegrationsResource($resource),
Transfer::GROUP_BACKUPS => $this->importBackupResource($resource),
Transfer::GROUP_SETTINGS => $this->importSettingsResource($resource),
Transfer::GROUP_DOMAINS => $this->importDomainsResource($resource),
default => throw new \Exception('Invalid resource group', Exception::CODE_VALIDATION),
};
} catch (\Throwable $e) {
Expand Down Expand Up @@ -3157,6 +3167,25 @@ public function importSettingsResource(Resource $resource): Resource
return $resource;
}

public function importDomainsResource(Resource $resource): Resource
{
switch ($resource->getName()) {
case Resource::TYPE_RULE:
/** @var Rule $resource */
$success = $this->createRule($resource);
if (!$success) {
return $resource;
}
break;
}

if ($resource->getStatus() !== Resource::STATUS_SKIPPED) {
$resource->setStatus(Resource::STATUS_SUCCESS);
}

return $resource;
}

protected function createProjectVariable(ProjectVariable $resource): bool
{
$existing = $this->dbForProject->findOne('variables', [
Expand Down Expand Up @@ -3295,6 +3324,91 @@ protected function createSMTP(SMTP $resource): bool
return true;
}

/**
* Auto-generated rules (default `.appwrite.network` domains for functions/sites)
* are recreated automatically on the destination when the parent Function/Site
* is migrated, so only manual rules need to be imported.
*
* Function/site IDs are preserved across migration, so the source
* `deploymentResourceId` is passed through directly.
*/
protected function createRule(Rule $resource): bool
{
if ($resource->getTrigger() !== 'manual') {
$resource->setStatus(Resource::STATUS_SKIPPED, 'Auto-generated rule, recreated by parent resource migration');
return false;
}

$type = $resource->getType();
$deploymentResourceType = $resource->getDeploymentResourceType();
$branch = $resource->getDeploymentVcsProviderBranch();

try {
switch ($type) {
case 'api':
$this->proxy->createAPIRule($resource->getDomain());
break;

case 'redirect':
$statusCode = match ($resource->getRedirectStatusCode()) {
301 => StatusCode::MOVEDPERMANENTLY301(),
302 => StatusCode::FOUND302(),
307 => StatusCode::TEMPORARYREDIRECT307(),
308 => StatusCode::PERMANENTREDIRECT308(),
default => StatusCode::MOVEDPERMANENTLY301(),
};

$resourceType = $deploymentResourceType === 'site'
? ProxyResourceType::SITE()
: ProxyResourceType::FUNCTION();

$this->proxy->createRedirectRule(
$resource->getDomain(),
$resource->getRedirectUrl(),
$statusCode,
$resource->getDeploymentResourceId(),
$resourceType,
);
break;

case 'deployment':
if ($deploymentResourceType === 'function') {
$this->proxy->createFunctionRule(
$resource->getDomain(),
$resource->getDeploymentResourceId(),
$branch !== '' ? $branch : null,
);
} elseif ($deploymentResourceType === 'site') {
$this->proxy->createSiteRule(
$resource->getDomain(),
$resource->getDeploymentResourceId(),
$branch !== '' ? $branch : null,
);
} else {
$resource->setStatus(Resource::STATUS_SKIPPED, 'Unsupported deployment resource type "' . $deploymentResourceType . '"');
return false;
}
break;

default:
$resource->setStatus(Resource::STATUS_SKIPPED, 'Unsupported rule type "' . $type . '"');
return false;
}
} catch (AppwriteException $e) {
// 409 means the domain is owned by another project/organization — the
// user has to release it there before re-running. Surface as a warning,
// not an error, so the rest of the migration continues.
if ($e->getCode() === 409) {
$resource->setStatus(Resource::STATUS_WARNING, 'Domain "' . $resource->getDomain() . '" is owned by another project. Remove it there and re-run the migration.');
return false;
}

throw $e;
}

return true;
}

protected function createWebhook(Webhook $resource): bool
{
$existing = $this->dbForPlatform->findOne('webhooks', [
Expand Down
4 changes: 4 additions & 0 deletions src/Migration/Resource.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ abstract class Resource implements \JsonSerializable
public const TYPE_SERVICES = 'services';
public const TYPE_SMTP = 'smtp';

// Domains
public const TYPE_RULE = 'rule';

// Messaging
public const TYPE_SUBSCRIBER = 'subscriber';
public const TYPE_MESSAGE = 'message';
Expand Down Expand Up @@ -135,6 +138,7 @@ abstract class Resource implements \JsonSerializable
self::TYPE_LABELS,
self::TYPE_SERVICES,
self::TYPE_SMTP,
self::TYPE_RULE,
self::TYPE_PROVIDER,
self::TYPE_TOPIC,
self::TYPE_SUBSCRIBER,
Expand Down
117 changes: 117 additions & 0 deletions src/Migration/Resources/Domains/Rule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

namespace Utopia\Migration\Resources\Domains;

use Utopia\Migration\Resource;
use Utopia\Migration\Transfer;

class Rule extends Resource
{
public function __construct(
string $id,
private readonly string $domain,
private readonly string $type,
private readonly string $trigger,
private readonly string $redirectUrl = '',
private readonly int $redirectStatusCode = 0,
private readonly string $deploymentResourceType = '',
private readonly string $deploymentResourceId = '',
private readonly string $deploymentVcsProviderBranch = '',
string $createdAt = '',
string $updatedAt = '',
) {
$this->id = $id;
$this->createdAt = $createdAt;
$this->updatedAt = $updatedAt;
}

/**
* @param array<string, mixed> $array
*/
public static function fromArray(array $array): self
{
return new self(
$array['id'],
$array['domain'],
$array['type'],
$array['trigger'] ?? 'manual',
(string) ($array['redirectUrl'] ?? ''),
(int) ($array['redirectStatusCode'] ?? 0),
(string) ($array['deploymentResourceType'] ?? ''),
(string) ($array['deploymentResourceId'] ?? ''),
(string) ($array['deploymentVcsProviderBranch'] ?? ''),
createdAt: $array['createdAt'] ?? '',
updatedAt: $array['updatedAt'] ?? '',
);
}

/**
* @return array<string, mixed>
*/
public function jsonSerialize(): array
{
return [
'id' => $this->id,
'domain' => $this->domain,
'type' => $this->type,
'trigger' => $this->trigger,
'redirectUrl' => $this->redirectUrl,
'redirectStatusCode' => $this->redirectStatusCode,
'deploymentResourceType' => $this->deploymentResourceType,
'deploymentResourceId' => $this->deploymentResourceId,
'deploymentVcsProviderBranch' => $this->deploymentVcsProviderBranch,
'createdAt' => $this->createdAt,
'updatedAt' => $this->updatedAt,
];
}

public static function getName(): string
{
return Resource::TYPE_RULE;
}

public function getGroup(): string
{
return Transfer::GROUP_DOMAINS;
}

public function getDomain(): string
{
return $this->domain;
}

public function getType(): string
{
return $this->type;
}

public function getTrigger(): string
{
return $this->trigger;
}

public function getRedirectUrl(): string
{
return $this->redirectUrl;
}

public function getRedirectStatusCode(): int
{
return $this->redirectStatusCode;
}

public function getDeploymentResourceType(): string
{
return $this->deploymentResourceType;
}

public function getDeploymentResourceId(): string
{
return $this->deploymentResourceId;
}

public function getDeploymentVcsProviderBranch(): string
{
return $this->deploymentVcsProviderBranch;
}
}
17 changes: 17 additions & 0 deletions src/Migration/Source.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ public function getSettingsBatchSize(): int
return static::$defaultBatchSize;
}

public function getDomainsBatchSize(): int
{
return static::$defaultBatchSize;
}

/**
* @param array<Resource> $resources
* @return void
Expand Down Expand Up @@ -127,6 +132,7 @@ public function exportResources(array $resources): void
Transfer::GROUP_INTEGRATIONS => Transfer::GROUP_INTEGRATIONS_RESOURCES,
Transfer::GROUP_BACKUPS => Transfer::GROUP_BACKUPS_RESOURCES,
Transfer::GROUP_SETTINGS => Transfer::GROUP_SETTINGS_RESOURCES,
Transfer::GROUP_DOMAINS => Transfer::GROUP_DOMAINS_RESOURCES,
];

foreach ($mapping as $group => $resources) {
Expand Down Expand Up @@ -170,6 +176,9 @@ public function exportResources(array $resources): void
case Transfer::GROUP_SETTINGS:
$this->exportGroupSettings($this->getSettingsBatchSize(), $resources);
break;
case Transfer::GROUP_DOMAINS:
$this->exportGroupDomains($this->getDomainsBatchSize(), $resources);
break;
}
}
}
Expand Down Expand Up @@ -245,4 +254,12 @@ abstract protected function exportGroupBackups(int $batchSize, array $resources)
* @param array<string> $resources Resources to export
*/
abstract protected function exportGroupSettings(int $batchSize, array $resources): void;

/**
* Export Domains Group
*
* @param int $batchSize
* @param array<string> $resources Resources to export
*/
abstract protected function exportGroupDomains(int $batchSize, array $resources): void;
}
Loading
Loading