From bb1f2d49986e52bfa232703ece57934a566076a8 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Thu, 21 May 2026 11:26:20 +0100 Subject: [PATCH] Add Custom Domains migration --- composer.json | 2 +- composer.lock | 16 ++--- src/Appwrite/Platform/Workers/Migrations.php | 2 + .../Utopia/Response/Model/MigrationReport.php | 6 ++ .../Services/Migrations/MigrationsBase.php | 65 +++++++++++++++++++ 5 files changed, 82 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index a90620aed2..e16437bc99 100644 --- a/composer.json +++ b/composer.json @@ -75,7 +75,7 @@ "utopia-php/lock": "0.2.*", "utopia-php/logger": "0.8.*", "utopia-php/messaging": "0.22.*", - "utopia-php/migration": "dev-add-smtp-migration as 1.12.0", + "utopia-php/migration": "dev-add-custom-domains-migration as 1.12.0", "utopia-php/platform": "1.0.0-rc2", "utopia-php/pools": "1.*", "utopia-php/span": "1.1.*", diff --git a/composer.lock b/composer.lock index 881ba4b12d..923a1e1888 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5f35f305036e8fc0a208c3adf4c7c16b", + "content-hash": "4d93c3da3ef7aa9fe898f609a6524431", "packages": [ { "name": "adhocore/jwt", @@ -4675,16 +4675,16 @@ }, { "name": "utopia-php/migration", - "version": "dev-add-smtp-migration", + "version": "dev-add-custom-domains-migration", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "e5dc08619aae087c6d45768225ea8d1e55eff63e" + "reference": "7af2ef5b766d3eb34fa8f83f2c43c84c853f499a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/e5dc08619aae087c6d45768225ea8d1e55eff63e", - "reference": "e5dc08619aae087c6d45768225ea8d1e55eff63e", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/7af2ef5b766d3eb34fa8f83f2c43c84c853f499a", + "reference": "7af2ef5b766d3eb34fa8f83f2c43c84c853f499a", "shasum": "" }, "require": { @@ -4741,10 +4741,10 @@ "utopia" ], "support": { - "source": "https://github.com/utopia-php/migration/tree/add-smtp-migration", + "source": "https://github.com/utopia-php/migration/tree/add-custom-domains-migration", "issues": "https://github.com/utopia-php/migration/issues" }, - "time": "2026-05-20T13:27:52+00:00" + "time": "2026-05-21T10:22:19+00:00" }, { "name": "utopia-php/mongo", @@ -8593,7 +8593,7 @@ }, { "package": "utopia-php/migration", - "version": "dev-add-smtp-migration", + "version": "dev-add-custom-domains-migration", "alias": "1.12.0", "alias_normalized": "1.12.0.0" } diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index 2dd59c7b4c..7826f1f957 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -389,6 +389,8 @@ class Migrations extends Action 'targets.write', 'webhooks.read', 'webhooks.write', + 'rules.read', + 'rules.write', 'project.read', 'project.write', 'keys.read', diff --git a/src/Appwrite/Utopia/Response/Model/MigrationReport.php b/src/Appwrite/Utopia/Response/Model/MigrationReport.php index ec8728ccf7..a0f83f2b9f 100644 --- a/src/Appwrite/Utopia/Response/Model/MigrationReport.php +++ b/src/Appwrite/Utopia/Response/Model/MigrationReport.php @@ -113,6 +113,12 @@ class MigrationReport extends Model 'default' => 0, 'example' => 1, ]) + ->addRule(Resource::TYPE_RULE, [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Number of custom-domain proxy rules to be migrated. Auto-generated `.appwrite.network` rules are skipped — they are recreated by parent Function/Site migration.', + 'default' => 0, + 'example' => 5, + ]) ->addRule(Resource::TYPE_SITE, [ 'type' => self::TYPE_INTEGER, 'description' => 'Number of sites to be migrated.', diff --git a/tests/e2e/Services/Migrations/MigrationsBase.php b/tests/e2e/Services/Migrations/MigrationsBase.php index 72b15d99b0..61284cfb58 100644 --- a/tests/e2e/Services/Migrations/MigrationsBase.php +++ b/tests/e2e/Services/Migrations/MigrationsBase.php @@ -3044,6 +3044,71 @@ trait MigrationsBase $this->client->call(Client::METHOD_PATCH, '/projects/' . $destinationProjectId . '/smtp', $consoleHeaders, $reset); } + public function testAppwriteMigrationCustomDomains(): void + { + $sourceHeaders = [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]; + + $destinationHeaders = [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getDestinationProject()['$id'], + 'x-appwrite-key' => $this->getDestinationProject()['apiKey'], + ]; + + // Unique domain so re-runs and parallel suites can't collide on the + // global domain uniqueness check. + $domain = \uniqid() . '-migration-api.myapp.com'; + + $createResp = $this->client->call(Client::METHOD_POST, '/proxy/rules/api', $sourceHeaders, [ + 'domain' => $domain, + ]); + $this->assertEquals(201, $createResp['headers']['status-code']); + $sourceRule = $createResp['body']; + + $result = $this->performMigrationSync([ + 'resources' => [ + Resource::TYPE_RULE, + ], + 'endpoint' => $this->webEndpoint, + 'projectId' => $this->getProject()['$id'], + 'apiKey' => $this->getProject()['apiKey'], + ]); + + $this->assertEquals('completed', $result['status']); + $this->assertEquals([Resource::TYPE_RULE], $result['resources']); + $this->assertArrayHasKey(Resource::TYPE_RULE, $result['statusCounters']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_RULE]['error']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_RULE]['pending']); + $this->assertGreaterThanOrEqual(1, $result['statusCounters'][Resource::TYPE_RULE]['success']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_RULE]['processing']); + $this->assertEquals(0, $result['statusCounters'][Resource::TYPE_RULE]['warning']); + + $listResp = $this->client->call(Client::METHOD_GET, '/proxy/rules', $destinationHeaders); + $this->assertEquals(200, $listResp['headers']['status-code']); + + $foundRule = null; + foreach ($listResp['body']['rules'] as $r) { + if ($r['domain'] === $domain) { + $foundRule = $r; + break; + } + } + + $this->assertNotNull($foundRule, 'Migrated rule not found on destination'); + $this->assertEquals($domain, $foundRule['domain']); + $this->assertEquals('api', $foundRule['type']); + $this->assertEquals('manual', $foundRule['trigger']); + + // Cleanup on destination + $this->client->call(Client::METHOD_DELETE, '/proxy/rules/' . $foundRule['$id'], $destinationHeaders); + + // Cleanup on source + $this->client->call(Client::METHOD_DELETE, '/proxy/rules/' . $sourceRule['$id'], $sourceHeaders); + } + /** * Import documents from a CSV file. */