diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Builds/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php similarity index 87% rename from src/Appwrite/Platform/Modules/Functions/Http/Deployments/Builds/Create.php rename to src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php index 1d85bcb11d..624a410dcd 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Builds/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Deployments/Duplicate/Create.php @@ -1,6 +1,6 @@ setHttpMethod(Action::HTTP_REQUEST_METHOD_POST) - ->setHttpPath('/v1/functions/:functionId/deployments/:deploymentId/build') + ->setHttpPath('/v1/functions/:functionId/deployments/duplicate') + ->httpAlias('/v1/functions/:functionId/deployments/:deploymentId/build') ->httpAlias('/v1/functions/:functionId/deployments/:deploymentId/builds/:buildId') - ->desc('Rebuild deployment') + ->desc('Create duplicate deployment') ->groups(['api', 'functions']) ->label('scope', 'functions.write') ->label('resourceType', RESOURCE_TYPE_FUNCTIONS) @@ -40,15 +41,15 @@ class Create extends Action ->label('audits.resource', 'function/{request.functionId}') ->label('sdk', new Method( namespace: 'functions', - name: 'createBuild', + name: 'createDuplicateDeployment', description: <<setParam('functionId', $function->getId()) ->setParam('deploymentId', $deployment->getId()); - $response->noContent(); + $response + ->setStatusCode(Response::STATUS_CODE_ACCEPTED) + ->dynamic($deployment, Response::MODEL_DEPLOYMENT); } } diff --git a/src/Appwrite/Platform/Modules/Functions/Services/Http.php b/src/Appwrite/Platform/Modules/Functions/Services/Http.php index 837dac6619..0e3763140e 100644 --- a/src/Appwrite/Platform/Modules/Functions/Services/Http.php +++ b/src/Appwrite/Platform/Modules/Functions/Services/Http.php @@ -2,11 +2,11 @@ namespace Appwrite\Platform\Modules\Functions\Services; -use Appwrite\Platform\Modules\Functions\Http\Deployments\Builds\Create as CreateBuild; use Appwrite\Platform\Modules\Functions\Http\Deployments\Builds\Update as UpdateBuild; use Appwrite\Platform\Modules\Functions\Http\Deployments\Create as CreateDeployment; use Appwrite\Platform\Modules\Functions\Http\Deployments\Delete as DeleteDeployment; use Appwrite\Platform\Modules\Functions\Http\Deployments\Download\Get as DownloadDeployment; +use Appwrite\Platform\Modules\Functions\Http\Deployments\Duplicate\Create as CreateDuplicateDeployment; use Appwrite\Platform\Modules\Functions\Http\Deployments\Get as GetDeployment; use Appwrite\Platform\Modules\Functions\Http\Deployments\Template\Create as CreateTemplateDeployment; use Appwrite\Platform\Modules\Functions\Http\Deployments\Vcs\Create as CreateVcsDeployment; @@ -62,7 +62,7 @@ class Http extends Service $this->addAction(CreateTemplateDeployment::getName(), new CreateTemplateDeployment()); $this->addAction(CreateVcsDeployment::getName(), new CreateVcsDeployment()); $this->addAction(DownloadDeployment::getName(), new DownloadDeployment()); - $this->addAction(CreateBuild::getName(), new CreateBuild()); + $this->addAction(CreateDuplicateDeployment::getName(), new CreateDuplicateDeployment()); $this->addAction(UpdateBuild::getName(), new UpdateBuild()); // Executions diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Builds/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php similarity index 89% rename from src/Appwrite/Platform/Modules/Sites/Http/Deployments/Builds/Create.php rename to src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php index 2f4da2d289..b30c07838f 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Builds/Create.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Deployments/Duplicate/Create.php @@ -1,6 +1,6 @@ setHttpMethod(Action::HTTP_REQUEST_METHOD_POST) - ->setHttpPath('/v1/sites/:siteId/deployments/:deploymentId/build') - ->desc('Rebuild deployment') + ->setHttpPath('/v1/sites/:siteId/deployments/duplicate') + ->desc('Create duplicate deployment') ->groups(['api', 'sites']) ->label('scope', 'sites.write') ->label('event', 'sites.[siteId].deployments.[deploymentId].update') @@ -41,15 +41,15 @@ class Create extends Action ->label('audits.resource', 'site/{request.siteId}') ->label('sdk', new Method( namespace: 'sites', - name: 'createDeploymentBuild', + name: 'createDuplicateDeployment', description: <<setParam('siteId', $site->getId()) ->setParam('deploymentId', $deployment->getId()); - $response->noContent(); + $response + ->setStatusCode(Response::STATUS_CODE_ACCEPTED) + ->dynamic($deployment, Response::MODEL_DEPLOYMENT); } } diff --git a/src/Appwrite/Platform/Modules/Sites/Services/Http.php b/src/Appwrite/Platform/Modules/Sites/Services/Http.php index 07dc2e5b8a..f8093b6f42 100644 --- a/src/Appwrite/Platform/Modules/Sites/Services/Http.php +++ b/src/Appwrite/Platform/Modules/Sites/Services/Http.php @@ -2,11 +2,11 @@ namespace Appwrite\Platform\Modules\Sites\Services; -use Appwrite\Platform\Modules\Sites\Http\Deployments\Builds\Create as CreateBuild; use Appwrite\Platform\Modules\Sites\Http\Deployments\Builds\Update as UpdateBuild; use Appwrite\Platform\Modules\Sites\Http\Deployments\Create as CreateDeployment; use Appwrite\Platform\Modules\Sites\Http\Deployments\Delete as DeleteDeployment; use Appwrite\Platform\Modules\Sites\Http\Deployments\Download\Get as DownloadDeployment; +use Appwrite\Platform\Modules\Sites\Http\Deployments\Duplicate\Create as CreateDuplicateDeployment; use Appwrite\Platform\Modules\Sites\Http\Deployments\Get as GetDeployment; use Appwrite\Platform\Modules\Sites\Http\Deployments\Template\Create as CreateTemplateDeployment; use Appwrite\Platform\Modules\Sites\Http\Deployments\Vcs\Create as CreateVcsDeployment; @@ -57,7 +57,7 @@ class Http extends Service $this->addAction(UpdateSiteDeployment::getName(), new UpdateSiteDeployment()); $this->addAction(DeleteDeployment::getName(), new DeleteDeployment()); $this->addAction(DownloadDeployment::getName(), new DownloadDeployment()); - $this->addAction(CreateBuild::getName(), new CreateBuild()); + $this->addAction(CreateDuplicateDeployment::getName(), new CreateDuplicateDeployment()); $this->addAction(UpdateBuild::getName(), new UpdateBuild()); // Logs diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index b55eea42ac..f812f57f00 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -87,6 +87,16 @@ trait FunctionsBase return $function; } + protected function updateFunction(string $functionId, mixed $params): mixed + { + $function = $this->client->call(Client::METHOD_PUT, '/functions/' . $functionId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), $params); + + return $function; + } + protected function createVariable(string $functionId, mixed $params): mixed { $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([ @@ -325,6 +335,39 @@ trait FunctionsBase return $response; } + protected function setupDuplicateDeployment(string $functionId, string $deploymentId): string + { + $deployment = $this->createDuplicateDeployment($functionId, $deploymentId); + $this->assertEquals(202, $deployment['headers']['status-code']); + + $deploymentId = $deployment['body']['$id']; + $this->assertNotEmpty($deploymentId); + + $this->assertEventually(function () use ($functionId, $deploymentId) { + $deployment = $this->getDeployment($functionId, $deploymentId); + $this->assertEquals('ready', $deployment['body']['status'], 'Deployment status is not ready, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); + }, 100000, 500); + + $this->assertEventually(function () use ($functionId, $deploymentId) { + $function = $this->getFunction($functionId); + $this->assertEquals($deploymentId, $function['body']['deployment'], 'Deployment is not activated, deployment: ' . json_encode($function['body'], JSON_PRETTY_PRINT)); + }, 100000, 500); + + return $deploymentId; + } + + protected function createDuplicateDeployment(string $functionId, string $deploymentId): mixed + { + $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments/duplicate', array_merge([ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'deploymentId' => $deploymentId, + ]); + + return $deployment; + } + protected function updateFunctionDeployment(string $functionId, string $deploymentId): mixed { $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployment', array_merge([ diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 481f4b8263..a1904edea1 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -1990,6 +1990,46 @@ class FunctionsCustomServerTest extends Scope $this->cleanupFunction($functionId); } + public function testDuplicateDeployment(): void + { + $functionId = $this->setupFunction([ + 'functionId' => ID::unique(), + 'runtime' => 'node-18.0', + 'name' => 'Duplicate Deployment Test', + 'entrypoint' => 'index.js', + 'commands' => '' + ]); + $this->assertNotEmpty($functionId); + + $deploymentId1 = $this->setupDeployment($functionId, [ + 'code' => $this->packageFunction('node'), + 'activate' => true + ]); + $this->assertNotEmpty($deploymentId1); + + $execution = $this->createExecution($functionId); + $this->assertEquals(201, $execution['headers']['status-code']); + $this->assertStringContainsString('APPWRITE_FUNCTION_ID', $execution['body']['responseBody']); + + $site = $this->updateFunction($functionId, [ + 'runtime' => 'node-18.0', + 'name' => 'Duplicate Deployment Test', + 'entrypoint' => 'index.js', + 'commands' => 'rm index.js && mv maintenance.js index.js' + ]); + $this->assertEquals(200, $site['headers']['status-code']); + $this->assertStringContainsString('maintenance.js', $site['body']['commands']); + + $deploymentId2 = $this->setupDuplicateDeployment($functionId, $deploymentId1); + $this->assertNotEmpty($deploymentId2); + + $execution = $this->createExecution($functionId); + $this->assertEquals(201, $execution['headers']['status-code']); + $this->assertStringContainsString('Maintenance', $execution['body']['responseBody']); + + $this->cleanupFunction($functionId); + } + public function testUpdateDeploymentStatus(): void { diff --git a/tests/e2e/Services/GraphQL/Base.php b/tests/e2e/Services/GraphQL/Base.php index 0b3250cecf..5c38a10472 100644 --- a/tests/e2e/Services/GraphQL/Base.php +++ b/tests/e2e/Services/GraphQL/Base.php @@ -1620,7 +1620,7 @@ trait Base }'; case self::$RETRY_BUILD: return 'mutation retryBuild($functionId: String!, $deploymentId: String!, $buildId: String!) { - functionsCreateBuild(functionId: $functionId, deploymentId: $deploymentId, buildId: $buildId) { + functionsCreateDuplicateDeployment(functionId: $functionId, deploymentId: $deploymentId, buildId: $buildId) { status } }'; diff --git a/tests/e2e/Services/GraphQL/FunctionsServerTest.php b/tests/e2e/Services/GraphQL/FunctionsServerTest.php index abd86b30ec..e45e75228b 100644 --- a/tests/e2e/Services/GraphQL/FunctionsServerTest.php +++ b/tests/e2e/Services/GraphQL/FunctionsServerTest.php @@ -186,8 +186,8 @@ class FunctionsServerTest extends Scope 'x-appwrite-project' => $projectId, ], $this->getHeaders()), $gqlPayload); - $this->assertIsNotArray($response['body']); - $this->assertEquals(204, $response['headers']['status-code']); + $this->assertIsArray($response['body']['data']); + $this->assertEquals(200, $response['headers']['status-code']); } public function testGetFunctions(): array diff --git a/tests/e2e/Services/Sites/SitesBase.php b/tests/e2e/Services/Sites/SitesBase.php index 08a87991b7..259bc85228 100644 --- a/tests/e2e/Services/Sites/SitesBase.php +++ b/tests/e2e/Services/Sites/SitesBase.php @@ -244,6 +244,39 @@ trait SitesBase return $deployment; } + protected function setupDuplicateDeployment(string $siteId, string $deploymentId): string + { + $deployment = $this->createDuplicateDeployment($siteId, $deploymentId); + $this->assertEquals(202, $deployment['headers']['status-code']); + + $deploymentId = $deployment['body']['$id']; + $this->assertNotEmpty($deploymentId); + + $this->assertEventually(function () use ($siteId, $deploymentId) { + $deployment = $this->getDeployment($siteId, $deploymentId); + $this->assertEquals('ready', $deployment['body']['status'], 'Deployment status is not ready, deployment: ' . json_encode($deployment['body'], JSON_PRETTY_PRINT)); + }, 100000, 500); + + $this->assertEventually(function () use ($siteId, $deploymentId) { + $site = $this->getSite($siteId); + $this->assertEquals($deploymentId, $site['body']['deploymentId'], 'Deployment is not activated, deployment: ' . json_encode($site['body'], JSON_PRETTY_PRINT)); + }, 100000, 500); + + return $deploymentId; + } + + protected function createDuplicateDeployment(string $siteId, string $deploymentId): mixed + { + $deployment = $this->client->call(Client::METHOD_POST, '/sites/' . $siteId . '/deployments/duplicate', array_merge([ + 'content-type' => 'multipart/form-data', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'deploymentId' => $deploymentId, + ]); + + return $deployment; + } + protected function createTemplateDeployment(string $siteId, mixed $params = []): mixed { $deployment = $this->client->call(Client::METHOD_POST, '/sites/' . $siteId . '/deployments/template', array_merge([ diff --git a/tests/e2e/Services/Sites/SitesCustomServerTest.php b/tests/e2e/Services/Sites/SitesCustomServerTest.php index 89e05cfe2f..605c5abb3e 100644 --- a/tests/e2e/Services/Sites/SitesCustomServerTest.php +++ b/tests/e2e/Services/Sites/SitesCustomServerTest.php @@ -1744,6 +1744,52 @@ class SitesCustomServerTest extends Scope $this->cleanupSite($siteId); } + public function testDuplicateDeployment(): void + { + $siteId = $this->setupSite([ + 'buildRuntime' => 'ssr-22', + 'framework' => 'other', + 'name' => 'Duplicate deployment Site', + 'adapter' => 'static', + 'fallbackFile' => '404.html', + 'siteId' => ID::unique() + ]); + $this->assertNotEmpty($siteId); + + $domain = $this->setupSiteDomain($siteId); + $this->assertNotEmpty($domain); + $proxyClient = new Client(); + $proxyClient->setEndpoint('http://' . $domain); + + $deploymentId1 = $this->setupDeployment($siteId, [ + 'code' => $this->packageSite('static-spa'), + 'activate' => true + ]); + $this->assertNotEmpty($deploymentId1); + + $response = $proxyClient->call(Client::METHOD_GET, '/not-found'); + $this->assertStringContainsString("Customized 404 page", $response['body']); + + $site = $this->updateSite([ + '$id' => $siteId, + 'buildRuntime' => 'ssr-22', + 'framework' => 'other', + 'name' => 'Duplicate deployment Site', + 'adapter' => 'static', + 'fallbackFile' => 'index.html', + ]); + $this->assertEquals(200, $site['headers']['status-code']); + $this->assertEquals('index.html', $site['body']['fallbackFile']); + + $deploymentId2 = $this->setupDuplicateDeployment($siteId, $deploymentId1); + $this->assertNotEmpty($deploymentId2); + + $response = $proxyClient->call(Client::METHOD_GET, '/not-found'); + $this->assertStringContainsString("Index page", $response['body']); + + $this->cleanupSite($siteId); + } + public function testUpdateDeploymentStatus(): void { $siteId = $this->setupSite([ diff --git a/tests/resources/functions/node/maintenance.js b/tests/resources/functions/node/maintenance.js new file mode 100644 index 0000000000..0885f11041 --- /dev/null +++ b/tests/resources/functions/node/maintenance.js @@ -0,0 +1,3 @@ +module.exports = async(context) => { + return context.res.send('Maintenance'); +} \ No newline at end of file