From 5eb52d312b03c91546268ec4f0d6b98b85156f74 Mon Sep 17 00:00:00 2001 From: Khushboo Verma Date: Wed, 27 Aug 2025 20:05:27 +0530 Subject: [PATCH 1/7] Add previewUrl to vcs comment from vcs controller --- app/controllers/api/vcs.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 9271e87448..5541b99318 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -271,6 +271,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); $domain = ID::unique() . "." . $sitesDomain; $ruleId = md5($domain); + $previewRuleId = $ruleId; Authorization::skip( fn () => $dbForPlatform->createDocument('rules', new Document([ '$id' => $ruleId, @@ -362,6 +363,24 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId } } + if ($resource->getCollection() === 'sites' && !empty($latestCommentId) && !empty($previewRuleId)) { + try { + $rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', $previewRuleId)); + + $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https'; + $previewUrl = !empty($rule) ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : ''; + + if (!empty($previewUrl)) { + $comment = new Comment(); + $comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId)); + $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, $previewUrl); + $github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()); + } + } catch (\Throwable $th) { + // Ignore, rule already exists; will be updated by builds worker + } + } + if (!empty($providerCommitHash) && $resource->getAttribute('providerSilentMode', false) === false) { $resourceName = $resource->getAttribute('name'); $projectName = $project->getAttribute('name'); From a737328bf038fb99197ba061a33897863c26f817 Mon Sep 17 00:00:00 2001 From: Khushboo Verma Date: Thu, 28 Aug 2025 12:22:55 +0530 Subject: [PATCH 2/7] Use branch url as preview url in comment --- app/controllers/api/vcs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 5541b99318..85b63916d7 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -271,7 +271,6 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); $domain = ID::unique() . "." . $sitesDomain; $ruleId = md5($domain); - $previewRuleId = $ruleId; Authorization::skip( fn () => $dbForPlatform->createDocument('rules', new Document([ '$id' => $ruleId, @@ -304,6 +303,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId $resourceProjectHash = substr(hash('sha256', $resource->getId() . $project->getId()), 0, 7); $domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}"; $ruleId = md5($domain); + $previewRuleId = $ruleId; try { Authorization::skip( fn () => $dbForPlatform->createDocument('rules', new Document([ From f3204d25207bc67ead344836da4edeae026a953e Mon Sep 17 00:00:00 2001 From: Khushboo Verma Date: Thu, 28 Aug 2025 17:51:37 +0530 Subject: [PATCH 3/7] Use branch domain in preview urls in builds worker --- .../Modules/Functions/Workers/Builds.php | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index 9547a752ef..18c746e9cd 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -1554,16 +1554,30 @@ class Builds extends Action default => throw new \Exception('Invalid resource type') }; - $rule = Authorization::skip(fn () => $dbForPlatform->findOne('rules', [ - Query::equal("projectInternalId", [$project->getSequence()]), - Query::equal("type", ["deployment"]), - Query::equal("deploymentInternalId", [$deployment->getSequence()]), - ])); - $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https'; $previewUrl = match($resource->getCollection()) { 'functions' => '', - 'sites' => !empty($rule) ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : '', + 'sites' => (function () use ($deployment, $project, $dbForPlatform, $protocol, $resource) { + $providerBranch = $deployment->getAttribute('providerBranch', ''); + if (!empty($providerBranch)) { + $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); + $branchPrefix = substr($providerBranch, 0, 16); + if (strlen($providerBranch) > 16) { + $remainingChars = substr($providerBranch, 16); + $branchPrefix .= '-' . substr(hash('sha256', $remainingChars), 0, 7); + } + $resourceProjectHash = substr(hash('sha256', $resource->getId() . $project->getId()), 0, 7); + $domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}"; + $ruleId = md5($domain); + + $branchRule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', $ruleId)); + + if (!empty($branchRule) && !$branchRule->isEmpty()) { + return "{$protocol}://" . $branchRule->getAttribute('domain', ''); + } + } + return ''; + })(), default => throw new \Exception('Invalid resource type') }; From 2f49722092aafa12581d250eaae70bec286bfc16 Mon Sep 17 00:00:00 2001 From: Khushboo Verma Date: Thu, 28 Aug 2025 19:17:07 +0530 Subject: [PATCH 4/7] Revert to use deployment preview --- app/controllers/api/vcs.php | 2 +- .../Modules/Functions/Workers/Builds.php | 28 +++++-------------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 85b63916d7..5541b99318 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -271,6 +271,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); $domain = ID::unique() . "." . $sitesDomain; $ruleId = md5($domain); + $previewRuleId = $ruleId; Authorization::skip( fn () => $dbForPlatform->createDocument('rules', new Document([ '$id' => $ruleId, @@ -303,7 +304,6 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId $resourceProjectHash = substr(hash('sha256', $resource->getId() . $project->getId()), 0, 7); $domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}"; $ruleId = md5($domain); - $previewRuleId = $ruleId; try { Authorization::skip( fn () => $dbForPlatform->createDocument('rules', new Document([ diff --git a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php index 18c746e9cd..9547a752ef 100644 --- a/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php +++ b/src/Appwrite/Platform/Modules/Functions/Workers/Builds.php @@ -1554,30 +1554,16 @@ class Builds extends Action default => throw new \Exception('Invalid resource type') }; + $rule = Authorization::skip(fn () => $dbForPlatform->findOne('rules', [ + Query::equal("projectInternalId", [$project->getSequence()]), + Query::equal("type", ["deployment"]), + Query::equal("deploymentInternalId", [$deployment->getSequence()]), + ])); + $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https'; $previewUrl = match($resource->getCollection()) { 'functions' => '', - 'sites' => (function () use ($deployment, $project, $dbForPlatform, $protocol, $resource) { - $providerBranch = $deployment->getAttribute('providerBranch', ''); - if (!empty($providerBranch)) { - $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); - $branchPrefix = substr($providerBranch, 0, 16); - if (strlen($providerBranch) > 16) { - $remainingChars = substr($providerBranch, 16); - $branchPrefix .= '-' . substr(hash('sha256', $remainingChars), 0, 7); - } - $resourceProjectHash = substr(hash('sha256', $resource->getId() . $project->getId()), 0, 7); - $domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}"; - $ruleId = md5($domain); - - $branchRule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', $ruleId)); - - if (!empty($branchRule) && !$branchRule->isEmpty()) { - return "{$protocol}://" . $branchRule->getAttribute('domain', ''); - } - } - return ''; - })(), + 'sites' => !empty($rule) ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : '', default => throw new \Exception('Invalid resource type') }; From b32e1ad930326c6b7e02a7c6924bbef2a96dfb72 Mon Sep 17 00:00:00 2001 From: Khushboo Verma Date: Wed, 3 Sep 2025 16:57:15 +0530 Subject: [PATCH 5/7] Add vcsCommentLock before updating comment --- app/controllers/api/vcs.php | 89 ++++++++++++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 10 deletions(-) diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 5541b99318..9448618cb7 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -135,11 +135,35 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId if (!$latestComment->isEmpty()) { $latestCommentId = $latestComment->getAttribute('providerCommentId', ''); - $comment = new Comment(); - $comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId)); - $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, ''); + $retries = 0; - $latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment())); + while (true) { + $retries++; + + try { + $dbForPlatform->createDocument('vcsCommentLocks', new Document([ + '$id' => $latestCommentId + ])); + break; + } catch (\Throwable $err) { + if ($retries >= 9) { + throw $err; + } + + \sleep(1); + } + } + + // Wrap in try/finally to ensure lock file gets deleted + try { + $comment = new Comment(); + $comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId)); + $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, ''); + + $latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment())); + } finally { + $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId); + } } else { $comment = new Comment(); $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, ''); @@ -177,11 +201,36 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId foreach ($latestComments as $comment) { $latestCommentId = $comment->getAttribute('providerCommentId', ''); - $comment = new Comment(); - $comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId)); - $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, ''); - $latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment())); + $retries = 0; + + while (true) { + $retries++; + + try { + $dbForPlatform->createDocument('vcsCommentLocks', new Document([ + '$id' => $latestCommentId + ])); + break; + } catch (\Throwable $err) { + if ($retries >= 9) { + throw $err; + } + + \sleep(1); + } + } + + // Wrap in try/finally to ensure lock file gets deleted + try { + $comment = new Comment(); + $comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId)); + $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, ''); + + $latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment())); + } finally { + $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId); + } } } @@ -364,6 +413,26 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId } if ($resource->getCollection() === 'sites' && !empty($latestCommentId) && !empty($previewRuleId)) { + $retries = 0; + + while (true) { + $retries++; + + try { + $dbForPlatform->createDocument('vcsCommentLocks', new Document([ + '$id' => $latestCommentId + ])); + break; + } catch (\Throwable $err) { + if ($retries >= 9) { + throw $err; + } + + \sleep(1); + } + } + + // Wrap in try/finally to ensure lock file gets deleted try { $rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', $previewRuleId)); @@ -376,8 +445,8 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, $previewUrl); $github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()); } - } catch (\Throwable $th) { - // Ignore, rule already exists; will be updated by builds worker + } finally { + $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId); } } From e3af59332c648b6456796045f56864a67f7bf03c Mon Sep 17 00:00:00 2001 From: Khushboo Verma Date: Wed, 3 Sep 2025 18:45:43 +0530 Subject: [PATCH 6/7] Replace err with warning --- app/controllers/api/vcs.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 9448618cb7..8ca0decacf 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -15,6 +15,7 @@ use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use Appwrite\Vcs\Comment; use Utopia\App; +use Utopia\CLI\Console; use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; @@ -147,7 +148,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId break; } catch (\Throwable $err) { if ($retries >= 9) { - throw $err; + Console::warning("Error creating vcs comment lock for " . $latestCommentId . ": " . $err->getMessage()); } \sleep(1); @@ -214,7 +215,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId break; } catch (\Throwable $err) { if ($retries >= 9) { - throw $err; + Console::warning("Error creating vcs comment lock for " . $latestCommentId . ": " . $err->getMessage()); } \sleep(1); @@ -425,7 +426,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId break; } catch (\Throwable $err) { if ($retries >= 9) { - throw $err; + Console::warning("Error creating vcs comment lock for " . $latestCommentId . ": " . $err->getMessage()); } \sleep(1); From 3aa163a21efc5a117fc6e97c28c0dc1a149e9394 Mon Sep 17 00:00:00 2001 From: Khushboo Verma Date: Thu, 4 Sep 2025 11:50:12 +0530 Subject: [PATCH 7/7] Update comment only if lock is acquired, else continue --- app/controllers/api/vcs.php | 74 +++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 8ca0decacf..5bda9961f3 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -137,14 +137,16 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId $latestCommentId = $latestComment->getAttribute('providerCommentId', ''); $retries = 0; + $lockAcquired = false; - while (true) { + while ($retries < 9) { $retries++; try { $dbForPlatform->createDocument('vcsCommentLocks', new Document([ '$id' => $latestCommentId ])); + $lockAcquired = true; break; } catch (\Throwable $err) { if ($retries >= 9) { @@ -155,15 +157,17 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId } } - // Wrap in try/finally to ensure lock file gets deleted - try { - $comment = new Comment(); - $comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId)); - $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, ''); + if ($lockAcquired) { + // Wrap in try/finally to ensure lock file gets deleted + try { + $comment = new Comment(); + $comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId)); + $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, ''); - $latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment())); - } finally { - $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId); + $latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment())); + } finally { + $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId); + } } } else { $comment = new Comment(); @@ -204,14 +208,16 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId $latestCommentId = $comment->getAttribute('providerCommentId', ''); $retries = 0; + $lockAcquired = false; - while (true) { + while ($retries < 9) { $retries++; try { $dbForPlatform->createDocument('vcsCommentLocks', new Document([ '$id' => $latestCommentId ])); + $lockAcquired = true; break; } catch (\Throwable $err) { if ($retries >= 9) { @@ -222,15 +228,17 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId } } - // Wrap in try/finally to ensure lock file gets deleted - try { - $comment = new Comment(); - $comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId)); - $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, ''); + if ($lockAcquired) { + // Wrap in try/finally to ensure lock file gets deleted + try { + $comment = new Comment(); + $comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId)); + $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, ''); - $latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment())); - } finally { - $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId); + $latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment())); + } finally { + $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId); + } } } } @@ -415,14 +423,16 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId if ($resource->getCollection() === 'sites' && !empty($latestCommentId) && !empty($previewRuleId)) { $retries = 0; + $lockAcquired = false; - while (true) { + while ($retries < 9) { $retries++; try { $dbForPlatform->createDocument('vcsCommentLocks', new Document([ '$id' => $latestCommentId ])); + $lockAcquired = true; break; } catch (\Throwable $err) { if ($retries >= 9) { @@ -433,21 +443,23 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId } } - // Wrap in try/finally to ensure lock file gets deleted - try { - $rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', $previewRuleId)); + if ($lockAcquired) { + // Wrap in try/finally to ensure lock file gets deleted + try { + $rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', $previewRuleId)); - $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https'; - $previewUrl = !empty($rule) ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : ''; + $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https'; + $previewUrl = !empty($rule) ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : ''; - if (!empty($previewUrl)) { - $comment = new Comment(); - $comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId)); - $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, $previewUrl); - $github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()); + if (!empty($previewUrl)) { + $comment = new Comment(); + $comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId)); + $comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, $previewUrl); + $github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()); + } + } finally { + $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId); } - } finally { - $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId); } }