mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6cffcc5b5 | ||
|
|
6d3bcf91b6 | ||
|
|
7a89884a0c | ||
|
|
57ddf0ac76 | ||
|
|
6e1b7f1a47 | ||
|
|
6c5e73de3a | ||
|
|
90e69b011f | ||
|
|
7cc97d4210 | ||
|
|
59a4f1ef12 | ||
|
|
979cb087e1 | ||
|
|
6f9bb1487c | ||
|
|
4114d9dcb1 | ||
|
|
90c96e9318 | ||
|
|
4228321d76 | ||
|
|
3b96abf02a |
@@ -2,6 +2,7 @@
|
||||
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Filter\BranchDomain as BranchDomainFilter;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -316,13 +317,12 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
|
||||
// VCS branch preview
|
||||
if (!empty($providerBranch)) {
|
||||
$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}";
|
||||
$domain = (new BranchDomainFilter())->apply([
|
||||
'branch' => $providerBranch,
|
||||
'resourceId' => $resource->getId(),
|
||||
'projectId' => $project->getId(),
|
||||
'sitesDomain' => $sitesDomain,
|
||||
]);
|
||||
$ruleId = md5($domain);
|
||||
try {
|
||||
$authorization->skip(
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Filter;
|
||||
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class BranchDomain implements Filter
|
||||
{
|
||||
/**
|
||||
* Maximum length for branch prefix in domain name
|
||||
*/
|
||||
public const BRANCH_PREFIX_MAX_LENGTH = 16;
|
||||
|
||||
/**
|
||||
* Length of hash suffix when branch name exceeds max length
|
||||
*/
|
||||
public const HASH_SUFFIX_LENGTH = 7;
|
||||
|
||||
/**
|
||||
* Pre-process branch name to a valid domain name.
|
||||
*
|
||||
* Input should be an array with:
|
||||
* - 'branch' (string): The branch name
|
||||
* - 'resourceId' (string): The resource ID (site or function)
|
||||
* - 'projectId' (string): The project ID
|
||||
* - 'sitesDomain' (string): The base sites domain
|
||||
*/
|
||||
public function apply(mixed $input): mixed
|
||||
{
|
||||
$branch = $input['branch'] ?? '';
|
||||
$resourceId = $input['resourceId'] ?? '';
|
||||
$projectId = $input['projectId'] ?? '';
|
||||
$sitesDomain = $input['sitesDomain'] ?? '';
|
||||
|
||||
$branchPrefix = $this->generateBranchPrefix($branch);
|
||||
$resourceProjectHash = substr(hash('sha256', $resourceId . $projectId), 0, self::HASH_SUFFIX_LENGTH);
|
||||
$domain = \strtolower("branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}");
|
||||
return $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a branch prefix for domain name from a branch name.
|
||||
* Takes up to 16 characters, sanitizes them for domain use,
|
||||
* and appends a hash suffix if the branch name is longer than 16 characters.
|
||||
*
|
||||
* @param string $branch The branch name
|
||||
* @return string The branch prefix for domain name
|
||||
*/
|
||||
private function generateBranchPrefix(string $branch): string
|
||||
{
|
||||
$branchPrefix = substr($branch, 0, self::BRANCH_PREFIX_MAX_LENGTH);
|
||||
$branchPrefix = $this->sanitizeBranchName($branchPrefix);
|
||||
|
||||
if (strlen($branch) > self::BRANCH_PREFIX_MAX_LENGTH) {
|
||||
$remainingChars = substr($branch, self::BRANCH_PREFIX_MAX_LENGTH);
|
||||
$branchPrefix .= '-' . substr(hash('sha256', $remainingChars), 0, self::HASH_SUFFIX_LENGTH);
|
||||
}
|
||||
|
||||
return $branchPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a branch name for use in a domain name.
|
||||
* Replaces any characters that are not alphanumeric or hyphens with hyphens,
|
||||
* and removes leading/trailing hyphens.
|
||||
*
|
||||
* @param string $branch The branch name to sanitize
|
||||
* @return string The sanitized branch name
|
||||
*/
|
||||
private function sanitizeBranchName(string $branch): string
|
||||
{
|
||||
$allowedChars = array_merge(
|
||||
Text::NUMBERS,
|
||||
Text::ALPHABET_UPPER,
|
||||
Text::ALPHABET_LOWER,
|
||||
['-']
|
||||
);
|
||||
$allowedCharsFlip = array_flip($allowedChars);
|
||||
|
||||
$sanitized = '';
|
||||
for ($i = 0; $i < \strlen($branch); $i++) {
|
||||
$char = $branch[$i];
|
||||
|
||||
if (isset($allowedCharsFlip[$char])) {
|
||||
$sanitized .= $char;
|
||||
} else {
|
||||
// Prevents two -- or more in a row
|
||||
if (strlen($sanitized) > 0 && $sanitized[strlen($sanitized) - 1] !== '-') {
|
||||
$sanitized .= '-';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return trim($sanitized, '-');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Filter;
|
||||
|
||||
interface Filter
|
||||
{
|
||||
public function apply(mixed $input): mixed;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace Appwrite\Platform\Modules\Compute;
|
||||
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Filter\BranchDomain as BranchDomainFilter;
|
||||
use Appwrite\Platform\Action;
|
||||
use Appwrite\Platform\Modules\Compute\Validator\Specification as SpecificationValidator;
|
||||
use Utopia\Config\Config;
|
||||
@@ -326,13 +327,12 @@ class Base extends Action
|
||||
|
||||
// VCS branch preview
|
||||
if (!empty($providerBranch)) {
|
||||
$branchPrefix = substr($providerBranch, 0, 16);
|
||||
if (strlen($providerBranch) > 16) {
|
||||
$remainingChars = substr($providerBranch, 16);
|
||||
$branchPrefix .= '-' . substr(hash('sha256', $remainingChars), 0, 7);
|
||||
}
|
||||
$resourceProjectHash = substr(hash('sha256', $site->getId() . $project->getId()), 0, 7);
|
||||
$domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}";
|
||||
$domain = (new BranchDomainFilter())->apply([
|
||||
'branch' => $providerBranch,
|
||||
'resourceId' => $site->getId(),
|
||||
'projectId' => $project->getId(),
|
||||
'sitesDomain' => $sitesDomain,
|
||||
]);
|
||||
$ruleId = md5($domain);
|
||||
try {
|
||||
$authorization->skip(
|
||||
|
||||
@@ -9,6 +9,7 @@ use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\Screenshot;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Filter\BranchDomain as BranchDomainFilter;
|
||||
use Appwrite\Utopia\Response\Model\Deployment;
|
||||
use Appwrite\Vcs\Comment;
|
||||
use Exception;
|
||||
@@ -1027,14 +1028,12 @@ class Builds extends Action
|
||||
// VCS branch
|
||||
$branchName = $deployment->getAttribute('providerBranch');
|
||||
if (!empty($branchName)) {
|
||||
$sitesDomain = $platform['sitesDomain'];
|
||||
$branchPrefix = substr($branchName, 0, 16);
|
||||
if (strlen($branchName) > 16) {
|
||||
$remainingChars = substr($branchName, 16);
|
||||
$branchPrefix .= '-' . substr(hash('sha256', $remainingChars), 0, 7);
|
||||
}
|
||||
$resourceProjectHash = substr(hash('sha256', $resource->getId() . $project->getId()), 0, 7);
|
||||
$domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}";
|
||||
$domain = (new BranchDomainFilter())->apply([
|
||||
'branch' => $branchName,
|
||||
'resourceId' => $resource->getId(),
|
||||
'projectId' => $project->getId(),
|
||||
'sitesDomain' => $platform['sitesDomain'],
|
||||
]);
|
||||
$ruleId = md5($domain);
|
||||
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Filter;
|
||||
|
||||
use Appwrite\Filter\BranchDomain as BranchDomainFilter;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class BranchDomainTest extends TestCase
|
||||
{
|
||||
public function testBranchDomain(): void
|
||||
{
|
||||
$filter = new BranchDomainFilter();
|
||||
|
||||
// Branch name with slash
|
||||
$domain = $filter->apply([
|
||||
'branch' => 'feature/test',
|
||||
'resourceId' => 'site123',
|
||||
'projectId' => 'proj456',
|
||||
'sitesDomain' => 'appwrite.network'
|
||||
]);
|
||||
$this->assertStringNotContainsString('/', $domain);
|
||||
$this->assertStringStartsWith('branch-feature-test-', $domain);
|
||||
$this->assertStringEndsWith('.appwrite.network', $domain);
|
||||
|
||||
// Branch domain consistency
|
||||
$domain2 = $filter->apply([
|
||||
'branch' => 'feature/test',
|
||||
'resourceId' => 'site123',
|
||||
'projectId' => 'proj456',
|
||||
'sitesDomain' => 'appwrite.network'
|
||||
]);
|
||||
$this->assertEquals($domain, $domain);
|
||||
|
||||
// Different resources should produce different domains
|
||||
$domain2 = $filter->apply([
|
||||
'branch' => 'feature/test',
|
||||
'resourceId' => 'site789',
|
||||
'projectId' => 'proj456',
|
||||
'sitesDomain' => 'appwrite.network'
|
||||
]);
|
||||
$this->assertNotEquals($domain, $domain2);
|
||||
|
||||
// Different projects should produce different domains
|
||||
$domain2 = $filter->apply([
|
||||
'branch' => 'feature/test',
|
||||
'resourceId' => 'site123',
|
||||
'projectId' => 'proj789',
|
||||
'sitesDomain' => 'appwrite.network'
|
||||
]);
|
||||
$this->assertNotEquals($domain, $domain2);
|
||||
|
||||
// Some real-world branch names
|
||||
$domain = $filter->apply([
|
||||
'branch' => 'feature/SER-1234',
|
||||
'resourceId' => 'site123',
|
||||
'projectId' => 'proj456',
|
||||
'sitesDomain' => 'appwrite.network'
|
||||
]);
|
||||
$this->assertStringStartsWith('branch-feature-ser-1234-', $domain);
|
||||
$this->assertStringEndsWith('.appwrite.network', $domain);
|
||||
|
||||
$domain = $filter->apply([
|
||||
'branch' => 'bugfix/fix-login',
|
||||
'resourceId' => 'site123',
|
||||
'projectId' => 'proj456',
|
||||
'sitesDomain' => 'appwrite.network'
|
||||
]);
|
||||
$this->assertStringStartsWith('branch-bugfix-fix-login-', $domain);
|
||||
$this->assertStringEndsWith('.appwrite.network', $domain);
|
||||
|
||||
$domain = $filter->apply([
|
||||
'branch' => 'hotfix/v1.2.3',
|
||||
'resourceId' => 'site123',
|
||||
'projectId' => 'proj456',
|
||||
'sitesDomain' => 'appwrite.network'
|
||||
]);
|
||||
$this->assertStringStartsWith('branch-hotfix-v1-2-3-', $domain);
|
||||
$this->assertStringEndsWith('.appwrite.network', $domain);
|
||||
|
||||
$domain = $filter->apply([
|
||||
'branch' => 'release/2024.01',
|
||||
'resourceId' => 'site123',
|
||||
'projectId' => 'proj456',
|
||||
'sitesDomain' => 'appwrite.network'
|
||||
]);
|
||||
$this->assertStringStartsWith('branch-release-2024-01-', $domain);
|
||||
$this->assertStringEndsWith('.appwrite.network', $domain);
|
||||
|
||||
$domain = $filter->apply([
|
||||
'branch' => 'user/john/experiment',
|
||||
'resourceId' => 'site123',
|
||||
'projectId' => 'proj456',
|
||||
'sitesDomain' => 'appwrite.network'
|
||||
]);
|
||||
$this->assertStringStartsWith('branch-user-john-experi-', $domain);
|
||||
$this->assertStringEndsWith('.appwrite.network', $domain);
|
||||
|
||||
$domain = $filter->apply([
|
||||
'branch' => 'dependabot/npm_and_yarn/lodash-4.17.21',
|
||||
'resourceId' => 'site123',
|
||||
'projectId' => 'proj456',
|
||||
'sitesDomain' => 'appwrite.network'
|
||||
]);
|
||||
$this->assertStringStartsWith('branch-dependabot-npm-a-', $domain);
|
||||
$this->assertStringEndsWith('.appwrite.network', $domain);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user