From 679de3574f23cbd091405f5c99150332bac72b89 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Sep 2025 19:11:33 +0000 Subject: [PATCH 1/6] Initial plan From fea3544d4bd7e89f4e3fd5dddccbc662a6ad5c43 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Sep 2025 19:18:07 +0000 Subject: [PATCH 2/6] Implement dynamic specification defaults for Functions and Sites Co-authored-by: stnguyen90 <1477010+stnguyen90@users.noreply.github.com> --- .../Platform/Modules/Compute/Base.php | 44 +++++++++++++++++++ .../Functions/Http/Functions/Create.php | 2 +- .../Functions/Http/Functions/Update.php | 2 +- .../Modules/Sites/Http/Sites/Create.php | 2 +- .../Modules/Sites/Http/Sites/Update.php | 2 +- 5 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Compute/Base.php b/src/Appwrite/Platform/Modules/Compute/Base.php index 92805fbaf8..7115747fb8 100644 --- a/src/Appwrite/Platform/Modules/Compute/Base.php +++ b/src/Appwrite/Platform/Modules/Compute/Base.php @@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Compute; use Appwrite\Event\Build; use Appwrite\Extend\Exception; use Appwrite\Platform\Action; +use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Exception\Duplicate; @@ -19,6 +20,49 @@ use Utopia\VCS\Exception\RepositoryNotFound; class Base extends Action { + /** + * Get default specification based on plan and available specifications. + * + * @param array $plan The billing plan configuration + * @return string The appropriate default specification + */ + protected function getDefaultSpecification(array $plan): string + { + $specifications = Config::getParam('specifications', []); + + if (empty($specifications)) { + return APP_COMPUTE_SPECIFICATION_DEFAULT; + } + + // If there's a plan with runtime specifications, use the highest one from the plan + if (!empty($plan) && array_key_exists('runtimeSpecifications', $plan) && !empty($plan['runtimeSpecifications'])) { + $planSpecifications = $plan['runtimeSpecifications']; + // Find the highest specification in the plan + foreach (array_reverse(array_keys($specifications)) as $specKey) { + if (in_array($specKey, $planSpecifications)) { + return $specKey; + } + } + } + + // If no plan or plan-based specification, use the highest available specification + $maxCpus = (float) System::getEnv('_APP_COMPUTE_CPUS', 0); + $maxMemory = (int) System::getEnv('_APP_COMPUTE_MEMORY', 0); + + $highestSpec = APP_COMPUTE_SPECIFICATION_DEFAULT; + foreach (array_reverse(array_keys($specifications)) as $specKey) { + $spec = $specifications[$specKey]; + $withinCpuLimit = empty($maxCpus) || $spec['cpus'] <= $maxCpus; + $withinMemoryLimit = empty($maxMemory) || $spec['memory'] <= $maxMemory; + + if ($withinCpuLimit && $withinMemoryLimit) { + $highestSpec = $specKey; + break; + } + } + + return $highestSpec; + } public function redeployVcsFunction(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Build $queueForBuilds, Document $template, GitHub $github, bool $activate, string $referenceType = 'branch', string $reference = ''): Document { $deploymentId = ID::unique(); diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php index 21a74f9a81..8e3e0c3772 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Create.php @@ -92,7 +92,7 @@ class Create extends Base ->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function.', true) ->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true) ->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true) - ->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification( + ->param('specification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification( $plan, Config::getParam('specifications', []), System::getEnv('_APP_COMPUTE_CPUS', 0), diff --git a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php index aaff953af0..318c2a2032 100644 --- a/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php +++ b/src/Appwrite/Platform/Modules/Functions/Http/Functions/Update.php @@ -89,7 +89,7 @@ class Update extends Base ->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function', true) ->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true) ->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true) - ->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification( + ->param('specification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification( $plan, Config::getParam('specifications', []), System::getEnv('_APP_COMPUTE_CPUS', 0), diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php index 9be95441cb..a1633b8eba 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Create.php @@ -78,7 +78,7 @@ class Create extends Base ->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the site.', true) ->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the site? In silent mode, comments will not be made on commits and pull requests.', true) ->param('providerRootDirectory', '', new Text(128, 0), 'Path to site code in the linked repo.', true) - ->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification( + ->param('specification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification( $plan, Config::getParam('specifications', []), System::getEnv('_APP_COMPUTE_CPUS', 0), diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php index 80354d5067..72ec04a2a5 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Sites/Update.php @@ -82,7 +82,7 @@ class Update extends Base ->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the site.', true) ->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the site? In silent mode, comments will not be made on commits and pull requests.', true) ->param('providerRootDirectory', '', new Text(128, 0), 'Path to site code in the linked repo.', true) - ->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification( + ->param('specification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification( $plan, Config::getParam('specifications', []), System::getEnv('_APP_COMPUTE_CPUS', 0), From 62014fda26633c1c77c4129ccf8f28ade1d85b93 Mon Sep 17 00:00:00 2001 From: Steven Nguyen <1477010+stnguyen90@users.noreply.github.com> Date: Mon, 15 Sep 2025 21:29:44 +0000 Subject: [PATCH 3/6] fix: implement dynamic specification defaults for Functions and Sites Prior to this, self-hosted instances would defualt to the lowest specification. Now, if there is no plan (ie. on self-hosted), the highest specification is used. If there is a plan, the lowest specification available in the plan is used. --- .../Platform/Modules/Compute/Base.php | 43 +++++++------------ 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Compute/Base.php b/src/Appwrite/Platform/Modules/Compute/Base.php index 7115747fb8..a538eb1497 100644 --- a/src/Appwrite/Platform/Modules/Compute/Base.php +++ b/src/Appwrite/Platform/Modules/Compute/Base.php @@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Compute; use Appwrite\Event\Build; use Appwrite\Extend\Exception; use Appwrite\Platform\Action; +use Appwrite\Platform\Modules\Compute\Validator\Specification as SpecificationValidator; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; @@ -29,40 +30,28 @@ class Base extends Action protected function getDefaultSpecification(array $plan): string { $specifications = Config::getParam('specifications', []); - + if (empty($specifications)) { return APP_COMPUTE_SPECIFICATION_DEFAULT; } - // If there's a plan with runtime specifications, use the highest one from the plan - if (!empty($plan) && array_key_exists('runtimeSpecifications', $plan) && !empty($plan['runtimeSpecifications'])) { - $planSpecifications = $plan['runtimeSpecifications']; - // Find the highest specification in the plan - foreach (array_reverse(array_keys($specifications)) as $specKey) { - if (in_array($specKey, $planSpecifications)) { - return $specKey; - } - } + $specificationValidator = new SpecificationValidator( + $plan, + $specifications, + System::getEnv('_APP_COMPUTE_CPUS', 0), + System::getEnv('_APP_COMPUTE_MEMORY', 0) + ); + $allowedSpecifications = $specificationValidator->getAllowedSpecifications(); + + // If there is no plan use the highest specification + if (empty($plan)) { + return end($allowedSpecifications) ?? APP_COMPUTE_SPECIFICATION_DEFAULT; } - // If no plan or plan-based specification, use the highest available specification - $maxCpus = (float) System::getEnv('_APP_COMPUTE_CPUS', 0); - $maxMemory = (int) System::getEnv('_APP_COMPUTE_MEMORY', 0); - - $highestSpec = APP_COMPUTE_SPECIFICATION_DEFAULT; - foreach (array_reverse(array_keys($specifications)) as $specKey) { - $spec = $specifications[$specKey]; - $withinCpuLimit = empty($maxCpus) || $spec['cpus'] <= $maxCpus; - $withinMemoryLimit = empty($maxMemory) || $spec['memory'] <= $maxMemory; - - if ($withinCpuLimit && $withinMemoryLimit) { - $highestSpec = $specKey; - break; - } - } - - return $highestSpec; + // Otherwise, use the lowest specification available in the plan + return $allowedSpecifications[0] ?? APP_COMPUTE_SPECIFICATION_DEFAULT; } + public function redeployVcsFunction(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Build $queueForBuilds, Document $template, GitHub $github, bool $activate, string $referenceType = 'branch', string $reference = ''): Document { $deploymentId = ID::unique(); From 536cff9cc65a8836d0b5821c1bec231b44913845 Mon Sep 17 00:00:00 2001 From: Steven Nguyen <1477010+stnguyen90@users.noreply.github.com> Date: Mon, 15 Sep 2025 21:30:18 +0000 Subject: [PATCH 4/6] chore: add specification validator test --- .../Compute/Validator/SpecificationTest.php | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 tests/unit/Platform/Modules/Compute/Validator/SpecificationTest.php diff --git a/tests/unit/Platform/Modules/Compute/Validator/SpecificationTest.php b/tests/unit/Platform/Modules/Compute/Validator/SpecificationTest.php new file mode 100644 index 0000000000..0505494e17 --- /dev/null +++ b/tests/unit/Platform/Modules/Compute/Validator/SpecificationTest.php @@ -0,0 +1,75 @@ +specifications = Config::getParam('specifications', []); + } + + public function testGetAllowedSpecificationsNoLimits(): void + { + $validator = new Specification( + plan: [], + specifications: $this->specifications, + maxCpus: 0, + maxMemory: 0 + ); + + $allowed = $validator->getAllowedSpecifications(); + $this->assertCount(count($this->specifications), $allowed); + $this->assertEquals( + $this->specifications[array_key_last($this->specifications)]['slug'], + $allowed[array_key_last($allowed)] + ); + } + + public function testGetAllowedSpecificationsWithMaxCpusAndMemory(): void + { + $validator = new Specification( + plan: [], + specifications: $this->specifications, + maxCpus: 2, + maxMemory: 2048 + ); + + $allowed = $validator->getAllowedSpecifications(); + $this->assertCount(4, $allowed); + $this->assertEquals( + SpecificationConstants::S_2VCPU_2GB, + $allowed[array_key_last($allowed)] + ); + } + + public function testGetAllowedSpecificationsWithPlanLimits(): void + { + $plan = [ + 'runtimeSpecifications' => [ + SpecificationConstants::S_05VCPU_512MB, + SpecificationConstants::S_1VCPU_512MB + ] + ]; + $validator = new Specification( + plan: $plan, + specifications: $this->specifications, + maxCpus: 0, + maxMemory: 0 + ); + + $allowed = $validator->getAllowedSpecifications(); + $this->assertCount(2, $allowed); + $this->assertContains(SpecificationConstants::S_05VCPU_512MB, $allowed); + $this->assertContains(SpecificationConstants::S_1VCPU_512MB, $allowed); + } +} From 5d5c1d8f43286c5baec29183ed77f2583c5d9bcb Mon Sep 17 00:00:00 2001 From: Khushboo Verma Date: Mon, 22 Sep 2025 20:29:25 +0530 Subject: [PATCH 5/6] Fix Author URL in template deployments --- app/init/constants.php | 1 + src/Appwrite/Platform/Modules/Functions/Workers/Builds.php | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/init/constants.php b/app/init/constants.php index 28cf8a4052..6d723e9182 100644 --- a/app/init/constants.php +++ b/app/init/constants.php @@ -85,6 +85,7 @@ const APP_PLATFORM_CLIENT = 'client'; const APP_PLATFORM_CONSOLE = 'console'; const APP_VCS_GITHUB_USERNAME = 'Appwrite'; const APP_VCS_GITHUB_EMAIL = 'team@appwrite.io'; +const APP_VCS_GITHUB_URL = 'https://github.com/TeamAppwrite'; // Database Reconnect const DATABASE_RECONNECT_SLEEP = 2; diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index 9547a752ef..c0d55aa7fb 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -467,11 +467,10 @@ class Builds extends Action } $providerCommitHash = \trim($stdout); - $authorUrl = "https://github.com/$cloneOwner"; $deployment->setAttribute('providerCommitHash', $providerCommitHash ?? ''); - $deployment->setAttribute('providerCommitAuthorUrl', $authorUrl); - $deployment->setAttribute('providerCommitAuthor', 'Appwrite'); + $deployment->setAttribute('providerCommitAuthorUrl', APP_VCS_GITHUB_URL); + $deployment->setAttribute('providerCommitAuthor', APP_VCS_GITHUB_USERNAME); $deployment->setAttribute('providerCommitMessage', "Create '" . $resource->getAttribute('name', '') . "' function"); $deployment->setAttribute('providerCommitUrl', "https://github.com/$cloneOwner/$cloneRepository/commit/$providerCommitHash"); $deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment); From 18371eb5116619c3cee3faf3b4388358160fe0fb Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 22 Oct 2025 17:00:26 +0100 Subject: [PATCH 6/6] fix: stats usage memory leak --- src/Appwrite/Platform/Workers/StatsUsage.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Appwrite/Platform/Workers/StatsUsage.php b/src/Appwrite/Platform/Workers/StatsUsage.php index 32cdb02dea..018c192647 100644 --- a/src/Appwrite/Platform/Workers/StatsUsage.php +++ b/src/Appwrite/Platform/Workers/StatsUsage.php @@ -435,7 +435,6 @@ class StatsUsage extends Action return $cmp; } - unset($this->projects[$sequence]); // Period ASC $cmp = strcmp($a['period'], $b['period']); if ($cmp !== 0) {