Merge pull request #11955 from appwrite/feat-ser-401-custom-triggers

Feat: custom triggers for VCS builds
This commit is contained in:
Harsh Mahajan
2026-05-21 19:08:38 +05:30
committed by GitHub
9 changed files with 123 additions and 10 deletions
+44
View File
@@ -841,6 +841,28 @@ return [
'array' => true,
'filters' => [],
],
[
'$id' => ID::custom('providerBranches'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => false,
'default' => [],
'array' => true,
'filters' => [],
],
[
'$id' => ID::custom('providerPaths'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => false,
'default' => [],
'array' => true,
'filters' => [],
],
],
'indexes' => [
[
@@ -1320,6 +1342,28 @@ return [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('providerBranches'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => false,
'default' => [],
'array' => true,
'filters' => [],
],
[
'$id' => ID::custom('providerPaths'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => false,
'default' => [],
'array' => true,
'filters' => [],
],
],
'indexes' => [
[
Generated
+6 -6
View File
@@ -5355,16 +5355,16 @@
},
{
"name": "utopia-php/validators",
"version": "0.2.3",
"version": "0.2.4",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/validators.git",
"reference": "9770269c8ed8e6909934965fa8722103c7434c23"
"reference": "b4ee60db4dbae5ffbe53968d01f69b6941251576"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/validators/zipball/9770269c8ed8e6909934965fa8722103c7434c23",
"reference": "9770269c8ed8e6909934965fa8722103c7434c23",
"url": "https://api.github.com/repos/utopia-php/validators/zipball/b4ee60db4dbae5ffbe53968d01f69b6941251576",
"reference": "b4ee60db4dbae5ffbe53968d01f69b6941251576",
"shasum": ""
},
"require": {
@@ -5394,9 +5394,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/validators/issues",
"source": "https://github.com/utopia-php/validators/tree/0.2.3"
"source": "https://github.com/utopia-php/validators/tree/0.2.4"
},
"time": "2026-05-14T08:05:44+00:00"
"time": "2026-05-21T12:47:43+00:00"
},
{
"name": "utopia-php/vcs",
@@ -95,6 +95,8 @@ 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('providerBranches', [], new ArrayList(new Text(128), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of branch name patterns to trigger automatic deployments. Supports wildcards. Leave empty to deploy on all branches.', true)
->param('providerPaths', [], new ArrayList(new Text(128), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of file path patterns to trigger automatic deployments. Supports wildcards. Leave empty to deploy on all file changes.', true)
->param('buildSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
$plan,
Config::getParam('specifications', []),
@@ -147,6 +149,8 @@ class Create extends Base
string $providerBranch,
bool $providerSilentMode,
string $providerRootDirectory,
array $providerBranches,
array $providerPaths,
string $buildSpecification,
string $runtimeSpecification,
string $templateRepository,
@@ -248,6 +252,8 @@ class Create extends Base
'providerBranch' => $providerBranch,
'providerRootDirectory' => $providerRootDirectory,
'providerSilentMode' => $providerSilentMode,
'providerBranches' => $providerBranches,
'providerPaths' => $providerPaths,
'buildSpecification' => $buildSpecification,
'runtimeSpecification' => $runtimeSpecification,
]));
@@ -87,6 +87,8 @@ 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('providerBranches', null, new Nullable(new ArrayList(new Text(128), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'List of branch name patterns to trigger automatic deployments. Supports wildcards. Leave empty to deploy on all branches.', true)
->param('providerPaths', null, new Nullable(new ArrayList(new Text(128), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'List of file path patterns to trigger automatic deployments. Supports wildcards. Leave empty to deploy on all file changes.', true)
->param('buildSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
$plan,
Config::getParam('specifications', []),
@@ -132,6 +134,8 @@ class Update extends Base
string $providerBranch,
bool $providerSilentMode,
string $providerRootDirectory,
?array $providerBranches,
?array $providerPaths,
string $buildSpecification,
string $runtimeSpecification,
int $deploymentRetention,
@@ -276,6 +280,8 @@ class Update extends Base
'providerBranch' => $providerBranch,
'providerRootDirectory' => $providerRootDirectory,
'providerSilentMode' => $providerSilentMode,
'providerBranches' => $providerBranches ?? $function->getAttribute('providerBranches', []),
'providerPaths' => $providerPaths ?? $function->getAttribute('providerPaths', []),
'buildSpecification' => $buildSpecification,
'runtimeSpecification' => $runtimeSpecification,
'search' => implode(' ', [$functionId, $name, $runtime]),
@@ -19,6 +19,7 @@ use Utopia\Database\Helpers\ID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
@@ -78,6 +79,8 @@ 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('providerBranches', [], new ArrayList(new Text(128), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of branch name patterns to trigger automatic deployments. Supports wildcards. Leave empty to deploy on all branches.', true)
->param('providerPaths', [], new ArrayList(new Text(128), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of file path patterns to trigger automatic deployments. Supports wildcards. Leave empty to deploy on all file changes.', true)
->param('buildSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
$plan,
Config::getParam('specifications', []),
@@ -118,6 +121,8 @@ class Create extends Base
string $providerBranch,
bool $providerSilentMode,
string $providerRootDirectory,
array $providerBranches,
array $providerPaths,
string $buildSpecification,
string $runtimeSpecification,
int $deploymentRetention,
@@ -173,6 +178,8 @@ class Create extends Base
'providerBranch' => $providerBranch,
'providerRootDirectory' => $providerRootDirectory,
'providerSilentMode' => $providerSilentMode,
'providerBranches' => $providerBranches,
'providerPaths' => $providerPaths,
'buildSpecification' => $buildSpecification,
'runtimeSpecification' => $runtimeSpecification,
'buildRuntime' => $buildRuntime,
@@ -22,7 +22,9 @@ use Utopia\Http\Adapter\Swoole\Request;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
@@ -81,6 +83,8 @@ 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('providerBranches', null, new Nullable(new ArrayList(new Text(128), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'List of branch name patterns to trigger automatic deployments. Supports wildcards. Leave empty to deploy on all branches.', true)
->param('providerPaths', null, new Nullable(new ArrayList(new Text(128), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'List of file path patterns to trigger automatic deployments. Supports wildcards. Leave empty to deploy on all file changes.', true)
->param('buildSpecification', fn (array $plan) => $this->getDefaultSpecification($plan), fn (array $plan) => new Specification(
$plan,
Config::getParam('specifications', []),
@@ -126,6 +130,8 @@ class Update extends Base
string $providerBranch,
bool $providerSilentMode,
string $providerRootDirectory,
?array $providerBranches,
?array $providerPaths,
string $buildSpecification,
string $runtimeSpecification,
int $deploymentRetention,
@@ -271,6 +277,8 @@ class Update extends Base
'providerBranch' => $providerBranch,
'providerRootDirectory' => $providerRootDirectory,
'providerSilentMode' => $providerSilentMode,
'providerBranches' => $providerBranches ?? $site->getAttribute('providerBranches', []),
'providerPaths' => $providerPaths ?? $site->getAttribute('providerPaths', []),
'buildSpecification' => $buildSpecification,
'runtimeSpecification' => $runtimeSpecification,
'search' => implode(' ', [$siteId, $name, $framework]),
@@ -130,7 +130,14 @@ class Update extends Action
$providerCommitAuthor = $commitDetails["commitAuthor"] ?? '';
$providerCommitAuthorUrl = $commitDetails["commitAuthorUrl"] ?? '';
$this->createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, true, $dbForPlatform, $authorization, $publisherForBuilds, $getProjectDB, $platform);
$prFiles = $github->getPullRequestFiles($owner, $providerRepositoryName, $providerPullRequestId);
$providerAffectedFiles = [
...array_column($prFiles, 'filename'),
// Only renamed files include previous_filename; skip missing values from other file changes.
...array_filter(array_column($prFiles, 'previous_filename'))
];
$this->createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $providerAffectedFiles, true, $dbForPlatform, $authorization, $publisherForBuilds, $getProjectDB, $platform);
$response->noContent();
}
@@ -22,6 +22,7 @@ use Utopia\DSN\DSN;
use Utopia\Span\Span;
use Utopia\System\System;
use Utopia\Validator\Contains;
use Utopia\Validator\Globstar;
use Utopia\VCS\Adapter\Git\GitHub;
use Utopia\VCS\Exception\RepositoryNotFound;
@@ -42,6 +43,7 @@ trait Deployment
string $providerCommitMessage,
string $providerCommitUrl,
string $providerPullRequestId,
array $providerAffectedFiles,
bool $external,
Database $dbForPlatform,
Authorization $authorization,
@@ -103,6 +105,32 @@ trait Deployment
continue;
}
// Skip deployments when the branch or affected files do not match configured build triggers.
$branchTrigger = new Globstar($resource->getAttribute('providerBranches', []));
if (!$branchTrigger->isValid($providerBranch)) {
Span::add("{$logBase}.build.skipped.reason", 'branch');
Span::add("{$logBase}.build.skipped", 'true');
continue;
}
$providerPaths = $resource->getAttribute('providerPaths', []);
if (!empty($providerPaths) && !empty($providerAffectedFiles)) {
$pathTrigger = new Globstar($providerPaths);
$pathMatched = false;
foreach ($providerAffectedFiles as $file) {
if ($pathTrigger->isValid($file)) {
$pathMatched = true;
break;
}
}
if (!$pathMatched) {
Span::add("{$logBase}.build.skipped.reason", 'path');
Span::add("{$logBase}.build.skipped", 'true');
continue;
}
}
$deploymentId = ID::unique();
$repositoryId = $repository->getId();
$repositoryInternalId = $repository->getSequence();
@@ -133,7 +133,6 @@ class Create extends Action
callable $getProjectDB,
array $platform,
) {
$providerBranchCreated = $parsedPayload["branchCreated"] ?? false;
$providerBranchDeleted = $parsedPayload["branchDeleted"] ?? false;
$providerBranch = $parsedPayload["branch"] ?? '';
$providerBranchUrl = $parsedPayload["branchUrl"] ?? '';
@@ -164,7 +163,8 @@ class Create extends Action
// Create new deployment only on push (not committed by us) and not when branch is deleted
if ($providerCommitAuthorEmail !== APP_VCS_GITHUB_EMAIL && !$providerBranchDeleted) {
$this->createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthorName, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $authorization, $publisherForBuilds, $getProjectDB, $platform);
$providerAffectedFiles = $parsedPayload['affectedFiles'] ?? [];
$this->createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthorName, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', $providerAffectedFiles, false, $dbForPlatform, $authorization, $publisherForBuilds, $getProjectDB, $platform);
}
}
@@ -211,12 +211,19 @@ class Create extends Action
$providerCommitAuthor = $commitDetails["commitAuthor"] ?? '';
$providerCommitMessage = $commitDetails["commitMessage"] ?? '';
$prFiles = $github->getPullRequestFiles($providerRepositoryOwner, $providerRepositoryName, $providerPullRequestId);
$providerAffectedFiles = [
...array_column($prFiles, 'filename'),
// Only renamed files include previous_filename; skip missing values from other file changes.
...array_filter(array_column($prFiles, 'previous_filename'))
];
$repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::orderDesc('$createdAt')
]));
$this->createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $authorization, $publisherForBuilds, $getProjectDB, $platform);
$this->createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $providerAffectedFiles, $external, $dbForPlatform, $authorization, $publisherForBuilds, $getProjectDB, $platform);
} elseif ($action == "closed") {
// Allowed external contributions cleanup