Create site endpoint working

This commit is contained in:
Khushboo Verma
2024-10-22 17:01:38 +02:00
parent 87cbd8fbf3
commit 5b0a05b4b3
26 changed files with 780 additions and 85 deletions
+2
View File
@@ -21,6 +21,7 @@ _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS=disabled
_APP_OPENSSL_KEY_V1=your-secret-key
_APP_DOMAIN=traefik
_APP_DOMAIN_FUNCTIONS=functions.localhost
_APP_DOMAIN_SITES=sites.localhost
_APP_DOMAIN_TARGET=localhost
_APP_REDIS_HOST=redis
_APP_REDIS_PORT=6379
@@ -77,6 +78,7 @@ _APP_FUNCTIONS_RUNTIMES_NETWORK=runtimes
_APP_EXECUTOR_SECRET=your-secret-key
_APP_EXECUTOR_HOST=http://proxy/v1
_APP_FUNCTIONS_RUNTIMES=php-8.0,node-18.0,python-3.9,ruby-3.1
_APP_SITES_FRAMEWORKS=sveltekit,nextjs
_APP_MAINTENANCE_INTERVAL=86400
_APP_MAINTENANCE_DELAY=
_APP_MAINTENANCE_RETENTION_CACHE=2592000
+10 -10
View File
@@ -3282,16 +3282,16 @@ $projectCollections = array_merge([
'default' => false,
'array' => false,
],
[
'$id' => ID::custom('logging'),
'type' => Database::VAR_BOOLEAN,
'signed' => true,
'size' => 0,
'format' => '',
'filters' => [],
'required' => true,
'array' => false,
],
// [
// '$id' => ID::custom('logging'),
// 'type' => Database::VAR_BOOLEAN,
// 'signed' => true,
// 'size' => 0,
// 'format' => '',
// 'filters' => [],
// 'required' => true,
// 'array' => false,
// ],
[
'$id' => ID::custom('framework'),
'type' => Database::VAR_STRING,
+7
View File
@@ -535,6 +535,13 @@ return [
'code' => 404,
],
/** Sites */
Exception::SITE_FRAMEWORK_UNSUPPORTED => [
'name' => Exception::SITE_FRAMEWORK_UNSUPPORTED,
'description' => 'The requested framework is either inactive or unsupported. Please check the value of the _APP_SITES_FRAMEWORKS environment variable.',
'code' => 404,
],
/** Builds */
Exception::BUILD_NOT_FOUND => [
'name' => Exception::BUILD_NOT_FOUND,
+28
View File
@@ -217,6 +217,34 @@ return [
],
]
],
'sites' => [
'$model' => Response::MODEL_SITE,
'$resource' => true,
'$description' => 'This event triggers on any sites event.',
'deployments' => [
'$model' => Response::MODEL_DEPLOYMENT,
'$resource' => true,
'$description' => 'This event triggers on any deployments event.',
'create' => [
'$description' => 'This event triggers when a deployment is created.',
],
'delete' => [
'$description' => 'This event triggers when a deployment is deleted.'
],
'update' => [
'$description' => 'This event triggers when a deployment is updated.'
],
],
'create' => [
'$description' => 'This event triggers when a site is created.'
],
'delete' => [
'$description' => 'This event triggers when a site is deleted.',
],
'update' => [
'$description' => 'This event triggers when a site is updated.',
]
],
'functions' => [
'$model' => Response::MODEL_FUNCTION,
'$resource' => true,
+7
View File
@@ -0,0 +1,7 @@
<?php
/**
* List of Appwrite Sites supported frameworks
*/
return ['sveltekit', 'nextjs'];
+4
View File
@@ -153,6 +153,9 @@ const APP_HOSTNAME_INTERNAL = 'appwrite';
const APP_FUNCTION_SPECIFICATION_DEFAULT = Specification::S_05VCPU_512MB;
const APP_FUNCTION_CPUS_DEFAULT = 0.5;
const APP_FUNCTION_MEMORY_DEFAULT = 512;
const APP_SITE_SPECIFICATION_DEFAULT = Specification::S_05VCPU_512MB;
const APP_SITE_CPUS_DEFAULT = 0.5;
const APP_SITE_MEMORY_DEFAULT = 512;
const APP_PLATFORM_SERVER = 'server';
const APP_PLATFORM_CLIENT = 'client';
const APP_PLATFORM_CONSOLE = 'console';
@@ -306,6 +309,7 @@ Config::load('errors', __DIR__ . '/config/errors.php');
Config::load('oAuthProviders', __DIR__ . '/config/oAuthProviders.php');
Config::load('platforms', __DIR__ . '/config/platforms.php');
Config::load('collections', __DIR__ . '/config/collections.php');
Config::load('frameworks', __DIR__ . '/config/frameworks.php');
Config::load('runtimes', __DIR__ . '/config/runtimes.php');
Config::load('runtimes-v2', __DIR__ . '/config/runtimes-v2.php');
Config::load('usage', __DIR__ . '/config/usage.php');
Generated
+13 -13
View File
@@ -2124,16 +2124,16 @@
},
{
"name": "utopia-php/messaging",
"version": "0.12.1",
"version": "0.12.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/messaging.git",
"reference": "b9dfafb5efc1d12cbee01d03dc98853ef026e35b"
"reference": "f6790fba1fcee12163d51c65d2c226a7856295d9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/b9dfafb5efc1d12cbee01d03dc98853ef026e35b",
"reference": "b9dfafb5efc1d12cbee01d03dc98853ef026e35b",
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/f6790fba1fcee12163d51c65d2c226a7856295d9",
"reference": "f6790fba1fcee12163d51c65d2c226a7856295d9",
"shasum": ""
},
"require": {
@@ -2169,9 +2169,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/messaging/issues",
"source": "https://github.com/utopia-php/messaging/tree/0.12.1"
"source": "https://github.com/utopia-php/messaging/tree/0.12.2"
},
"time": "2024-10-09T08:17:07+00:00"
"time": "2024-10-22T01:02:20+00:00"
},
{
"name": "utopia-php/migration",
@@ -2341,16 +2341,16 @@
},
{
"name": "utopia-php/platform",
"version": "0.7.0",
"version": "0.7.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/platform.git",
"reference": "beeea0f2c9bce14a6869fc5c87a1047cdecb5c52"
"reference": "3433a0f1a54988f2a59c735f507745cb2c24638a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/platform/zipball/beeea0f2c9bce14a6869fc5c87a1047cdecb5c52",
"reference": "beeea0f2c9bce14a6869fc5c87a1047cdecb5c52",
"url": "https://api.github.com/repos/utopia-php/platform/zipball/3433a0f1a54988f2a59c735f507745cb2c24638a",
"reference": "3433a0f1a54988f2a59c735f507745cb2c24638a",
"shasum": ""
},
"require": {
@@ -2385,9 +2385,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/platform/issues",
"source": "https://github.com/utopia-php/platform/tree/0.7.0"
"source": "https://github.com/utopia-php/platform/tree/0.7.1"
},
"time": "2024-05-08T17:00:55+00:00"
"time": "2024-10-22T10:27:49+00:00"
},
{
"name": "utopia-php/pools",
@@ -7029,5 +7029,5 @@
"platform-overrides": {
"php": "8.3"
},
"plugin-api-version": "2.6.0"
"plugin-api-version": "2.3.0"
}
+3
View File
@@ -24,6 +24,9 @@ class Event
public const FUNCTIONS_QUEUE_NAME = 'v1-functions';
public const FUNCTIONS_CLASS_NAME = 'FunctionsV1';
public const SITES_QUEUE_NAME = 'v1-sites';
public const SITES_CLASS_NAME = 'SitesV1';
public const USAGE_QUEUE_NAME = 'v1-usage';
public const USAGE_CLASS_NAME = 'UsageV1';
+3
View File
@@ -151,6 +151,9 @@ class Exception extends \Exception
public const PROVIDER_CONTRIBUTION_CONFLICT = 'provider_contribution_conflict';
public const GENERAL_PROVIDER_FAILURE = 'general_provider_failure';
/** Sites */
public const SITE_FRAMEWORK_UNSUPPORTED = 'site_framework_unsupported';
/** Functions */
public const FUNCTION_NOT_FOUND = 'function_not_found';
public const FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported';
+4 -2
View File
@@ -2,8 +2,9 @@
namespace Appwrite\Platform;
use Appwrite\Platform\Modules\Compute\Functions\Functions;
use Appwrite\Platform\Modules\Core;
use Appwrite\Platform\Modules\Functions;
use Appwrite\Platform\Modules\Sites;
use Utopia\Platform\Platform;
class Appwrite extends Platform
@@ -11,6 +12,7 @@ class Appwrite extends Platform
public function __construct()
{
parent::__construct(new Core());
$this->addModule(new Functions());
$this->addModule(new Functions\Module());
$this->addModule(new Sites\Module());
}
}
@@ -1,6 +1,6 @@
<?php
namespace Appwrite\Platform\Modules\Compute\Functions\Http\Functions;
namespace Appwrite\Platform\Modules\Compute;
use Appwrite\Event\Build;
use Appwrite\Extend\Exception;
@@ -10,14 +10,15 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Platform\Action;
use Utopia\Swoole\Request;
use Utopia\System\System;
use Utopia\VCS\Adapter\Git\GitHub;
use Utopia\VCS\Exception\RepositoryNotFound;
class Helper
class Base extends Action
{
public function redeployVcs(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Build $queueForBuilds, Document $template, GitHub $github)
public function redeployVcsFunction(Request $request, Document $function, Document $project, Document $installation, Database $dbForProject, Build $queueForBuilds, Document $template, GitHub $github)
{
$deploymentId = ID::unique();
$entrypoint = $function->getAttribute('entrypoint', '');
@@ -90,4 +91,79 @@ class Helper
->setDeployment($deployment)
->setTemplate($template);
}
public function redeployVcsSite(Request $request, Document $site, Document $project, Document $installation, Database $dbForProject, Build $queueForBuilds, Document $template, GitHub $github)
{
$deploymentId = ID::unique();
$providerInstallationId = $installation->getAttribute('providerInstallationId', '');
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($providerInstallationId);
$providerRepositoryId = $site->getAttribute('providerRepositoryId', '');
try {
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
if (empty($repositoryName)) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
} catch (RepositoryNotFound $e) {
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
}
$providerBranch = $site->getAttribute('providerBranch', 'main');
$authorUrl = "https://github.com/$owner";
$repositoryUrl = "https://github.com/$owner/$repositoryName";
$branchUrl = "https://github.com/$owner/$repositoryName/tree/$providerBranch";
$commitDetails = [];
if ($template->isEmpty()) {
try {
$commitDetails = $github->getLatestCommit($owner, $repositoryName, $providerBranch);
} catch (\Throwable $error) {
Console::warning('Failed to get latest commit details');
Console::warning($error->getMessage());
Console::warning($error->getTraceAsString());
}
}
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceId' => $site->getId(),
'resourceInternalId' => $site->getInternalId(),
'resourceType' => 'sites',
'buildCommand' => $site->getAttribute('buildCommand', ''),
'installCommand' => $site->getAttribute('installCommand', ''),
'outputDirectory' => $site->getAttribute('outputDirectory', ''),
'type' => 'vcs',
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
'repositoryId' => $site->getAttribute('repositoryId', ''),
'repositoryInternalId' => $site->getAttribute('repositoryInternalId', ''),
'providerBranchUrl' => $branchUrl,
'providerRepositoryName' => $repositoryName,
'providerRepositoryOwner' => $owner,
'providerRepositoryUrl' => $repositoryUrl,
'providerCommitHash' => $commitDetails['commitHash'] ?? '',
'providerCommitAuthorUrl' => $authorUrl,
'providerCommitAuthor' => $commitDetails['commitAuthor'] ?? '',
'providerCommitMessage' => $commitDetails['commitMessage'] ?? '',
'providerCommitUrl' => $commitDetails['commitUrl'] ?? '',
'providerBranch' => $providerBranch,
'providerRootDirectory' => $site->getAttribute('providerRootDirectory', ''),
'search' => implode(' ', [$deploymentId]),
'activate' => true,
]));
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($site)
->setDeployment($deployment)
->setTemplate($template);
}
}
@@ -1,14 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Compute\Functions;
use Appwrite\Platform\Modules\Compute\Functions\Services\Http;
use Utopia\Platform\Module;
class Functions extends Module
{
public function __construct()
{
$this->addService('http', new Http());
}
}
@@ -1,13 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Compute\Sites\Services;
use Utopia\Platform\Service;
class Http extends Service
{
public function __construct()
{
$this->type = Service::TYPE_HTTP;
}
}
@@ -1,14 +0,0 @@
<?php
namespace Appwrite\Platform\Modules\Compute\Sites;
use Appwrite\Platform\Modules\Compute\Sites\Services\Http;
use Utopia\Platform\Module;
class Sites extends Module
{
public function __construct()
{
$this->addService('http', new Http());
}
}
@@ -1,6 +1,6 @@
<?php
namespace Appwrite\Platform\Modules\Compute\Functions\Http\Deployments;
namespace Appwrite\Platform\Modules\Functions\Http\Deployments;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
@@ -58,7 +58,7 @@ class CreateDeployment extends Action
->param('functionId', '', new UID(), 'Function ID.')
->param('entrypoint', null, new Text(1028), 'Entrypoint File.', true)
->param('commands', null, new Text(8192, 0), 'Build Commands.', true)
->param('code', [], new File(), 'Gzip file with your code package. When used with the Appwrite CLI, pass the path to your code directory, and the CLI will automatically package your code. Use a path that is within the current directory.', true) // TODO: Add skip validation later
->param('code', [], new File(), 'Gzip file with your code package. When used with the Appwrite CLI, pass the path to your code directory, and the CLI will automatically package your code. Use a path that is within the current directory.', skipValidation: true)
->param('activate', false, new Boolean(true), 'Automatically activate the deployment when it is finished building.')
->inject('request')
->inject('response')
@@ -1,6 +1,6 @@
<?php
namespace Appwrite\Platform\Modules\Compute\Functions\Http\Functions;
namespace Appwrite\Platform\Modules\Functions\Http\Functions;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
@@ -8,6 +8,7 @@ use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Extend\Exception;
use Appwrite\Functions\Validator\RuntimeSpecification;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Task\Validator\Cron;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Response;
@@ -33,10 +34,9 @@ use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class CreateFunction extends Action
class CreateFunction extends Base
{
use HTTP;
private $helper;
public static function getName()
{
@@ -45,7 +45,6 @@ class CreateFunction extends Action
public function __construct()
{
$this->helper = new Helper();
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/functions')
@@ -214,7 +213,7 @@ class CreateFunction extends Action
if (!empty($providerRepositoryId)) {
// Deploy VCS
$this->helper->redeployVcs($request, $function, $project, $installation, $dbForProject, $queueForBuilds, $template, $github);
$this->redeployVcsFunction($request, $function, $project, $installation, $dbForProject, $queueForBuilds, $template, $github);
} elseif (!$template->isEmpty()) {
// Deploy non-VCS from template
$deploymentId = ID::unique();
@@ -1,12 +1,13 @@
<?php
namespace Appwrite\Platform\Modules\Compute\Functions\Http\Functions;
namespace Appwrite\Platform\Modules\Functions\Http\Functions;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Event\Validator\FunctionEvent;
use Appwrite\Extend\Exception;
use Appwrite\Functions\Validator\RuntimeSpecification;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Task\Validator\Cron;
use Appwrite\Utopia\Response;
use Executor\Executor;
@@ -34,10 +35,9 @@ use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class UpdateFunction extends Action
class UpdateFunction extends Base
{
use HTTP;
private $helper;
public static function getName()
{
@@ -46,7 +46,6 @@ class UpdateFunction extends Action
public function __construct()
{
$this->helper = new Helper();
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT)
->setHttpPath('/v1/functions/:functionId')
->desc('Update function')
@@ -238,7 +237,7 @@ class UpdateFunction extends Action
// Redeploy logic
if (!$isConnected && !empty($providerRepositoryId)) {
$this->helper->redeployVcs($request, $function, $project, $installation, $dbForProject, $queueForBuilds, new Document(), $github);
$this->redeployVcsFunction($request, $function, $project, $installation, $dbForProject, $queueForBuilds, new Document(), $github);
}
// Inform scheduler if function is still active
@@ -0,0 +1,14 @@
<?php
namespace Appwrite\Platform\Modules\Functions;
use Appwrite\Platform\Modules\Functions\Services\Http;
use Utopia\Platform;
class Module extends Platform\Module
{
public function __construct()
{
$this->addService('http', new Http());
}
}
@@ -1,10 +1,10 @@
<?php
namespace Appwrite\Platform\Modules\Compute\Functions\Services;
namespace Appwrite\Platform\Modules\Functions\Services;
use Appwrite\Platform\Modules\Compute\Functions\Http\Deployments\CreateDeployment;
use Appwrite\Platform\Modules\Compute\Functions\Http\Functions\CreateFunction;
use Appwrite\Platform\Modules\Compute\Functions\Http\Functions\UpdateFunction;
use Appwrite\Platform\Modules\Functions\Http\Deployments\CreateDeployment;
use Appwrite\Platform\Modules\Functions\Http\Functions\CreateFunction;
use Appwrite\Platform\Modules\Functions\Http\Functions\UpdateFunction;
use Utopia\Platform\Service;
class Http extends Service
@@ -0,0 +1,287 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Http\Sites;
use Appwrite\Event\Build;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\Sites\Validator\FrameworkSpecification;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model\Rule;
use Utopia\App;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Swoole\Request;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Utopia\VCS\Adapter\Git\GitHub;
class CreateSite extends Base
{
use HTTP;
public static function getName()
{
return 'createSite';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
->setHttpPath('/v1/sites')
->desc('Create site')
->groups(['api', 'sites'])
->label('scope', 'functions.write') // TODO: Update scope to sites.write
->label('event', 'sites.[siteId].create')
->label('audits.event', 'site.create')
->label('audits.resource', 'site/{response.$id}')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'sites')
->label('sdk.method', 'create')
->label('sdk.description', '/docs/references/sites/create-site.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_SITE)
->param('siteId', '', new CustomId(), 'Site ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('name', '', new Text(128), 'Site name. Max length: 128 chars.')
->param('framework', '', new WhiteList(Config::getParam('frameworks'), true), 'Sites framework.')
->param('enabled', true, new Boolean(), 'Is site enabled? When set to \'disabled\', users cannot access the site but Server SDKs with and API key can still access the site. No data is lost when this is toggled.', true) // TODO: Add logging param later
->param('installCommand', '', new Text(8192, 0), 'Install Command.', true)
->param('buildCommand', '', new Text(8192, 0), 'Build Command.', true)
->param('outputDirectory', '', new Text(8192, 0), 'Output Directory for site.', true)
->param('fallbackRedirect', '', new Text(8192, 0), 'Fallback Redirect URL for site in case a route is not found.', true)
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for API key auto-generated for every execution. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', true) //TODO: Update description of scopes
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Control System) deployment.', true)
->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the site.', true)
->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('templateRepository', '', new Text(128, 0), 'Repository name of the template.', true)
->param('templateOwner', '', new Text(128, 0), 'The name of the owner of the template.', true)
->param('templateRootDirectory', '', new Text(128, 0), 'Path to site code in the template repo.', true)
->param('templateVersion', '', new Text(128, 0), 'Version (tag) for the repo linked to the site template.', true)
->param('specification', APP_SITE_SPECIFICATION_DEFAULT, fn (array $plan) => new FrameworkSpecification(
$plan,
Config::getParam('framework-specifications', []),
App::getEnv('_APP_SITES_CPUS', APP_SITE_CPUS_DEFAULT),
App::getEnv('_APP_SITES_MEMORY', APP_SITE_MEMORY_DEFAULT)
), 'Runtime specification for the site and builds.', true, ['plan'])
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('user')
->inject('queueForEvents')
->inject('queueForBuilds')
->inject('dbForConsole')
->inject('gitHub')
->callback(fn ($siteId, $name, $framework, $enabled, $installCommand, $buildCommand, $outputDirectory, $fallbackRedirect, $scopes, $installationId, $providerRepositoryId, $providerBranch, $providerSilentMode, $providerRootDirectory, $templateRepository, $templateOwner, $templateRootDirectory, $templateVersion, $specification, $request, $response, $dbForProject, $project, $user, $queueForEvents, $queueForBuilds, $dbForConsole, $github) => $this->action($siteId, $name, $framework, $enabled, $installCommand, $buildCommand, $outputDirectory, $fallbackRedirect, $scopes, $installationId, $providerRepositoryId, $providerBranch, $providerSilentMode, $providerRootDirectory, $templateRepository, $templateOwner, $templateRootDirectory, $templateVersion, $specification, $request, $response, $dbForProject, $project, $user, $queueForEvents, $queueForBuilds, $dbForConsole, $github));
}
public function action(string $siteId, string $name, string $framework, bool $enabled, string $installCommand, string $buildCommand, string $outputDirectory, string $fallbackRedirect, array $scopes, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github)
{
$siteId = ($siteId == 'unique()') ? ID::unique() : $siteId;
$allowList = \array_filter(\explode(',', System::getEnv('_APP_SITES_FRAMEWORKS', '')));
if (!empty($allowList) && !\in_array($framework, $allowList)) {
throw new Exception(Exception::SITE_FRAMEWORK_UNSUPPORTED, 'Framework "' . $framework . '" is not supported');
}
// build from template
$template = new Document([]);
if (
!empty($templateRepository)
&& !empty($templateOwner)
&& !empty($templateRootDirectory)
&& !empty($templateVersion)
) {
$template->setAttribute('repositoryName', $templateRepository)
->setAttribute('ownerName', $templateOwner)
->setAttribute('rootDirectory', $templateRootDirectory)
->setAttribute('version', $templateVersion);
}
$installation = $dbForConsole->getDocument('installations', $installationId);
if (!empty($installationId) && $installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
if (!empty($providerRepositoryId) && (empty($installationId) || empty($providerBranch))) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'When connecting to VCS (Version Control System), you need to provide "installationId" and "providerBranch".');
}
$site = $dbForProject->createDocument('sites', new Document([
'$id' => $siteId,
'enabled' => $enabled,
'live' => true,
'name' => $name,
'framework' => $framework,
'deploymentInternalId' => '',
'deploymentId' => '',
'installCommand' => $installCommand,
'buildCommand' => $buildCommand,
'outputDirectory' => $outputDirectory,
'fallbackRedirect' => $fallbackRedirect,
'scopes' => $scopes,
'search' => implode(' ', [$siteId, $name, $framework]),
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
'repositoryId' => '',
'repositoryInternalId' => '',
'providerBranch' => $providerBranch,
'providerRootDirectory' => $providerRootDirectory,
'providerSilentMode' => $providerSilentMode,
'specification' => $specification
]));
// Git connect logic
if (!empty($providerRepositoryId)) {
$teamId = $project->getAttribute('teamId', '');
$repository = $dbForConsole->createDocument('repositories', new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::team(ID::custom($teamId))),
Permission::update(Role::team(ID::custom($teamId), 'owner')),
Permission::update(Role::team(ID::custom($teamId), 'developer')),
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
],
'installationId' => $installation->getId(),
'installationInternalId' => $installation->getInternalId(),
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'providerRepositoryId' => $providerRepositoryId,
'resourceId' => $site->getId(),
'resourceInternalId' => $site->getInternalId(),
'resourceType' => 'site',
'providerPullRequestIds' => []
]));
$site->setAttribute('repositoryId', $repository->getId());
$site->setAttribute('repositoryInternalId', $repository->getInternalId());
}
$site = $dbForProject->updateDocument('sites', $site->getId(), $site);
if (!empty($providerRepositoryId)) {
// Deploy VCS
$this->redeployVcsSite($request, $site, $project, $installation, $dbForProject, $queueForBuilds, $template, $github);
} elseif (!$template->isEmpty()) {
// Deploy non-VCS from template
$deploymentId = ID::unique();
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceId' => $site->getId(),
'resourceInternalId' => $site->getInternalId(),
'resourceType' => 'sites',
'installCommand' => $site->getAttribute('installCommand', ''),
'buildCommand' => $site->getAttribute('buildCommand', ''),
'outputDirectory' => $site->getAttribute('outputDirectory', ''),
'type' => 'manual',
'search' => implode(' ', [$deploymentId]),
'activate' => true,
]));
$queueForBuilds
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($site)
->setDeployment($deployment)
->setTemplate($template);
}
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
if (!empty($sitesDomain)) {
$ruleId = ID::unique();
$routeSubdomain = ID::unique();
$domain = "{$routeSubdomain}.{$sitesDomain}";
$rule = Authorization::skip(
fn () => $dbForConsole->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'domain' => $domain,
'resourceType' => 'site',
'resourceId' => $site->getId(),
'resourceInternalId' => $site->getInternalId(),
'status' => 'verified',
'certificateId' => '',
]))
);
/** Trigger Webhook */
$ruleModel = new Rule();
$ruleCreate =
$queueForEvents
->setClass(Event::WEBHOOK_CLASS_NAME)
->setQueue(Event::WEBHOOK_QUEUE_NAME);
$ruleCreate
->setProject($project)
->setEvent('rules.[ruleId].create')
->setParam('ruleId', $rule->getId())
->setPayload($rule->getArrayCopy(array_keys($ruleModel->getRules())))
->trigger();
/** Trigger Sites */
$ruleCreate
->setClass(Event::SITES_CLASS_NAME)
->setQueue(Event::SITES_QUEUE_NAME)
->trigger();
/** Trigger realtime event */
$allEvents = Event::generateEvents('rules.[ruleId].create', [
'ruleId' => $rule->getId(),
]);
$target = Realtime::fromPayload(
// Pass first, most verbose event pattern
event: $allEvents[0],
payload: $rule,
project: $project
);
Realtime::send(
projectId: 'console',
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
Realtime::send(
projectId: $project->getId(),
payload: $rule->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
}
$queueForEvents->setParam('siteId', $site->getId());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($site, Response::MODEL_SITE);
}
}
@@ -0,0 +1,14 @@
<?php
namespace Appwrite\Platform\Modules\Sites;
use Appwrite\Platform\Modules\Sites\Services\Http;
use Utopia\Platform;
class Module extends Platform\Module
{
public function __construct()
{
$this->addService('http', new Http());
}
}
@@ -0,0 +1,15 @@
<?php
namespace Appwrite\Platform\Modules\Sites\Services;
use Appwrite\Platform\Modules\Sites\Http\Sites\CreateSite;
use Utopia\Platform\Service;
class Http extends Service
{
public function __construct()
{
$this->type = Service::TYPE_HTTP;
$this->addAction(CreateSite::getName(), new CreateSite());
}
}
@@ -0,0 +1,112 @@
<?php
namespace Appwrite\Sites\Validator;
use Utopia\Validator;
class FrameworkSpecification extends Validator
{
private array $plan;
private array $specifications;
private float $maxCpus;
private int $maxMemory;
public function __construct(array $plan, array $specifications, float $maxCpus, int $maxMemory)
{
$this->plan = $plan;
$this->specifications = $specifications;
$this->maxCpus = $maxCpus;
$this->maxMemory = $maxMemory;
}
/**
* Get Allowed Specifications.
*
* Get allowed specifications taking into account the limits set by the environment variables and the plan.
*
* @return array
*/
public function getAllowedSpecifications(): array
{
$allowedSpecifications = [];
foreach ($this->specifications as $size => $values) {
if ($values['cpus'] <= $this->maxCpus && $values['memory'] <= $this->maxMemory) {
if (!empty($this->plan) && array_key_exists('frameworkSpecifications', $this->plan)) {
if (!\in_array($size, $this->plan['frameworkSpecifications'])) {
continue;
}
}
$allowedSpecifications[] = $size;
}
}
return $allowedSpecifications;
}
/**
* Get Description.
*
* Returns validator description.
*
* @return string
*/
public function getDescription(): string
{
return 'Specification must be one of: ' . implode(', ', $this->getAllowedSpecifications());
}
/**
* Is valid.
*
* Returns true if valid or false if not.
*
* @param mixed $value
*
* @return bool
*/
public function isValid($value): bool
{
if (empty($value)) {
return false;
}
if (!\is_string($value)) {
return false;
}
if (!\in_array($value, $this->getAllowedSpecifications())) {
return false;
}
return true;
}
/**
* Is array.
*
* Function will return true if object is array.
*
* @return bool
*/
public function isArray(): bool
{
return false;
}
/**
* Get Type.
*
* Returns validator type.
*
* @return string
*/
public function getType(): string
{
return self::TYPE_STRING;
}
}
+7
View File
@@ -84,6 +84,7 @@ use Appwrite\Utopia\Response\Model\ProviderRepository;
use Appwrite\Utopia\Response\Model\Rule;
use Appwrite\Utopia\Response\Model\Runtime;
use Appwrite\Utopia\Response\Model\Session;
use Appwrite\Utopia\Response\Model\Site;
use Appwrite\Utopia\Response\Model\Specification;
use Appwrite\Utopia\Response\Model\Subscriber;
use Appwrite\Utopia\Response\Model\Target;
@@ -244,6 +245,10 @@ class Response extends SwooleResponse
public const MODEL_VCS_CONTENT = 'vcsContent';
public const MODEL_VCS_CONTENT_LIST = 'vcsContentList';
// Sites
public const MODEL_SITE = 'site';
public const MODEL_SITE_LIST = 'siteList';
// Functions
public const MODEL_FUNCTION = 'function';
public const MODEL_FUNCTION_LIST = 'functionList';
@@ -351,6 +356,7 @@ class Response extends SwooleResponse
->setModel(new BaseList('Buckets List', self::MODEL_BUCKET_LIST, 'buckets', self::MODEL_BUCKET))
->setModel(new BaseList('Teams List', self::MODEL_TEAM_LIST, 'teams', self::MODEL_TEAM))
->setModel(new BaseList('Memberships List', self::MODEL_MEMBERSHIP_LIST, 'memberships', self::MODEL_MEMBERSHIP))
->setModel(new BaseList('Sites List', self::MODEL_SITE_LIST, 'sites', self::MODEL_SITE))
->setModel(new BaseList('Functions List', self::MODEL_FUNCTION_LIST, 'functions', self::MODEL_FUNCTION))
->setModel(new BaseList('Function Templates List', self::MODEL_TEMPLATE_FUNCTION_LIST, 'templates', self::MODEL_TEMPLATE_FUNCTION))
->setModel(new BaseList('Installations List', self::MODEL_INSTALLATION_LIST, 'installations', self::MODEL_INSTALLATION))
@@ -422,6 +428,7 @@ class Response extends SwooleResponse
->setModel(new Bucket())
->setModel(new Team())
->setModel(new Membership())
->setModel(new Site())
->setModel(new Func())
->setModel(new TemplateFunction())
->setModel(new TemplateRuntime())
+157
View File
@@ -0,0 +1,157 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Site extends Model
{
public function __construct()
{
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'Site ID.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('$createdAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Site creation date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('$updatedAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Site update date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Site name.',
'default' => '',
'example' => 'My Site',
])
->addRule('enabled', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Site enabled.',
'default' => true,
'example' => false,
])
->addRule('live', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Is the site deployed with the latest configuration? This is set to false if you\'ve changed an environment variables, entrypoint, commands, or other settings that needs redeploy to be applied. When the value is false, redeploy the site to update it with the latest configuration.',
'default' => true,
'example' => false,
])
->addRule('framework', [
'type' => self::TYPE_STRING,
'description' => 'Site framework.',
'default' => '',
'example' => 'react',
])
->addRule('deploymentId', [
'type' => self::TYPE_STRING,
'description' => 'Site\'s active deployment ID.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('scopes', [
'type' => self::TYPE_STRING,
'description' => 'Allowed permission scopes.',
'default' => [],
'example' => 'users.read',
'array' => true,
])
->addRule('vars', [
'type' => Response::MODEL_VARIABLE,
'description' => 'Site variables.',
'default' => [],
'example' => [],
'array' => true
])
->addRule('installCommand', [
'type' => self::TYPE_STRING,
'description' => 'The install command used to install the site dependencies.',
'default' => '',
'example' => 'npm install',
])
->addRule('buildCommand', [
'type' => self::TYPE_STRING,
'description' => 'The build command used to build the site.',
'default' => '',
'example' => 'npm run build',
])
->addRule('outputDirectory', [
'type' => self::TYPE_STRING,
'description' => 'The directory where the site build output is located.',
'default' => '',
'example' => 'build',
])
->addRule('fallbackRedirect', [
'type' => self::TYPE_STRING,
'description' => 'The URL to redirect to if the route is not found.', //TODO: Update the description
'default' => '',
'example' => 'https://appwrite.io',
])
->addRule('installationId', [
'type' => self::TYPE_STRING,
'description' => 'Site VCS (Version Control System) installation id.',
'default' => '',
'example' => '6m40at4ejk5h2u9s1hboo',
])
->addRule('providerRepositoryId', [
'type' => self::TYPE_STRING,
'description' => 'VCS (Version Control System) Repository ID',
'default' => '',
'example' => 'appwrite',
])
->addRule('providerBranch', [
'type' => self::TYPE_STRING,
'description' => 'VCS (Version Control System) branch name',
'default' => '',
'example' => 'main',
])
->addRule('providerRootDirectory', [
'type' => self::TYPE_STRING,
'description' => 'Path to site in VCS (Version Control System) repository',
'default' => '',
'example' => 'sites/helloWorld',
])
->addRule('providerSilentMode', [
'type' => self::TYPE_BOOLEAN,
'description' => 'Is VCS (Version Control System) connection is in silent mode? When in silence mode, no comments will be posted on the repository pull or merge requests',
'default' => false,
'example' => false,
])
->addRule('specification', [
'type' => self::TYPE_STRING,
'description' => 'Machine specification for builds and executions.',
'default' => APP_SITE_SPECIFICATION_DEFAULT,
'example' => APP_SITE_SPECIFICATION_DEFAULT,
])
;
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Site';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_SITE;
}
}