1
0
Fork 0
mirror of synced 2024-06-14 00:34:51 +12:00

Copy Khushboo's integration from feat-peach-q1-kh

This commit is contained in:
Matej Bačo 2023-05-22 12:58:13 +02:00
parent 6766508c95
commit 6e515e3cc4
20 changed files with 1335 additions and 11 deletions

5
.env
View file

@ -80,4 +80,7 @@ _APP_REGION=default
_APP_DOCKER_HUB_USERNAME=
_APP_DOCKER_HUB_PASSWORD=
_APP_CONSOLE_GITHUB_SECRET=
_APP_CONSOLE_GITHUB_APP_ID=
_APP_CONSOLE_GITHUB_APP_ID=
VCS_GITHUB_APP_NAME=
VCS_GITHUB_PRIVATE_KEY=
VCS_GITHUB_APP_ID=

View file

@ -1007,7 +1007,7 @@ $collections = [
[
'$id' => ID::custom('_key_region_resourceType_resourceUpdatedAt'),
'type' => Database::INDEX_KEY,
'attributes' => ['region', 'resourceType','resourceUpdatedAt'],
'attributes' => ['region', 'resourceType', 'resourceUpdatedAt'],
'lengths' => [],
'orders' => [],
],
@ -2221,6 +2221,178 @@ $collections = [
],
],
'vcs_installations' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('vcs_installations'),
'name' => 'vcs_installations',
'attributes' => [
[
'$id' => ID::custom('projectId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('projectInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('installationId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('organization'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('provider'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('accessToken'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['encrypt']
]
],
'indexes' => [],
],
'vcs_repos' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('vcs_repos'),
'name' => 'vcs_repos',
'attributes' => [
[
'$id' => ID::custom('vcsInstallationId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => []
],
[
'$id' => ID::custom('vcsInstallationInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('projectId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => []
],
[
'$id' => ID::custom('projectInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('repositoryId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => []
],
[
'$id' => ID::custom('repositoryOwner'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => []
],
[
'$id' => ID::custom('resourceId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceType'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => []
]
],
'indexes' => [],
],
'functions' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('functions'),
@ -2258,6 +2430,48 @@ $collections = [
'required' => true,
'array' => false,
],
[
'$id' => ID::custom('vcsInstallationId'),
'type' => Database::VAR_STRING,
'signed' => true,
'size' => 2048,
'format' => '',
'filters' => [],
'required' => false,
'array' => false,
],
[
'$id' => ID::custom('vcsInstallationInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('vcsRepoId'),
'type' => Database::VAR_STRING,
'signed' => true,
'size' => 2048,
'format' => '',
'filters' => [],
'required' => false,
'array' => false,
],
[
'$id' => ID::custom('vcsRepoInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('logging'),
'type' => Database::VAR_BOOLEAN,
@ -2445,6 +2659,20 @@ $collections = [
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_vcsInstallationId'),
'type' => Database::INDEX_KEY,
'attributes' => ['vcsInstallationId'],
'lengths' => [768],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_vcsRepoId'),
'type' => Database::INDEX_KEY,
'attributes' => ['vcsRepoId'],
'lengths' => [768],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_runtime'),
'type' => Database::INDEX_KEY,
@ -2601,6 +2829,77 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('type'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 2048,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('vcsInstallationId'),
'type' => Database::VAR_STRING,
'signed' => true,
'size' => Database::LENGTH_KEY,
'format' => '',
'filters' => [],
'required' => false,
'array' => false,
],
[
'$id' => ID::custom('vcsInstallationInternalId'),
'type' => Database::VAR_STRING,
'signed' => true,
'size' => Database::LENGTH_KEY,
'format' => '',
'filters' => [],
'required' => false,
'array' => false,
],
[
'$id' => ID::custom('vcsRepoId'),
'type' => Database::VAR_STRING,
'signed' => true,
'size' => Database::LENGTH_KEY,
'format' => '',
'filters' => [],
'required' => false,
'array' => false,
],
[
'$id' => ID::custom('vcsRepoInternalId'),
'type' => Database::VAR_STRING,
'signed' => true,
'size' => Database::LENGTH_KEY,
'format' => '',
'filters' => [],
'required' => false,
'array' => false,
],
[
'$id' => ID::custom('branch'),
'type' => Database::VAR_STRING,
'signed' => true,
'size' => Database::LENGTH_KEY,
'format' => '',
'filters' => [],
'required' => false,
'array' => false,
],
[
'$id' => ID::custom('vcsCommentId'),
'type' => Database::VAR_STRING,
'signed' => true,
'size' => 2048,
'format' => '',
'filters' => [],
'required' => false,
'array' => false,
],
[
'$id' => ID::custom('size'),
'type' => Database::VAR_INTEGER,
@ -3549,7 +3848,7 @@ $collections = [
'array' => false,
'filters' => [],
],
],
],
'indexes' => [
[
'$id' => '_key_accessedAt',
@ -3878,7 +4177,7 @@ $collections = [
'required' => true,
'default' => null,
'array' => false,
'filters' => [ 'encrypt' ]
'filters' => ['encrypt']
],
[
'$id' => ID::custom('search'),

View file

@ -324,6 +324,13 @@ return [
'code' => 416,
],
/** VCS */
Exception::INSTALLATION_NOT_FOUND => [
'name' => Exception::INSTALLATION_NOT_FOUND,
'description' => 'Installation with the requested ID could not be found.',
'code' => 404,
],
/** Functions */
Exception::FUNCTION_NOT_FOUND => [
'name' => Exception::FUNCTION_NOT_FOUND,

View file

@ -160,6 +160,19 @@ return [
'optional' => true,
'icon' => '/images/services/users.png',
],
'vcs' => [
'key' => 'vcs',
'name' => 'VCS',
'subtitle' => 'The VCS service allows you to interact with providers like GitHub, GitLab etc.',
'description' => '',
'controller' => 'api/vcs.php',
'sdk' => false,
'docs' => false,
'docsUrl' => '',
'tests' => false,
'optional' => true,
'icon' => '',
],
'functions' => [
'key' => 'functions',
'name' => 'Functions',

View file

@ -80,6 +80,7 @@ App::post('/v1/functions')
->inject('events')
->inject('dbForConsole')
->action(function (string $functionId, string $name, array $execute, string $runtime, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $buildCommand, string $installCommand, Response $response, Database $dbForProject, Document $project, Document $user, Event $eventsInstance, Database $dbForConsole) {
// TODO: Add support to link to GitHub repos from createFunction as well
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
$function = $dbForProject->createDocument('functions', new Document([
@ -522,13 +523,17 @@ App::put('/v1/functions/:functionId')
->param('entrypoint', '', new Text('1028'), 'Entrypoint File.', true)
->param('buildCommand', '', new Text('1028'), 'Build Command.', true)
->param('installCommand', '', new Text('1028'), 'Install Command.', true)
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for vcs deployment.', true)
->param('repositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the function', true)
->param('repositoryOwner', '', new Text(128, 0), 'Repository Owner of the repo linked to the function', true)
->inject('response')
->inject('dbForProject')
->inject('dbForConsole')
->inject('project')
->inject('user')
->inject('events')
->inject('dbForConsole')
->action(function (string $functionId, string $name, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $buildCommand, string $installCommand, Response $response, Database $dbForProject, Document $project, Document $user, Event $eventsInstance, Database $dbForConsole) {
->action(function (string $functionId, string $name, array $execute, array $events, string $schedule, int $timeout, bool $enabled, bool $logging, string $entrypoint, string $buildCommand, string $installCommand, string $vcsInstallationId, string $repositoryId, string $repositoryOwner, Response $response, Database $dbForProject, Document $project, Document $user, Event $eventsInstance, Database $dbForConsole) {
$function = $dbForProject->getDocument('functions', $functionId);
@ -536,8 +541,92 @@ App::put('/v1/functions/:functionId')
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$installation = $dbForConsole->getDocument('vcs_installations', $vcsInstallationId, [
Query::equal('projectInternalId', [$project->getInternalId()])
]);
if (!empty($vcsInstallationId) && $installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
if (!empty($vcsInstallationId) && empty($repositoryId)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID); // TODO: More specific error
}
if (!empty($repositoryId) && empty($vcsInstallationId)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID); // TODO: More specific error
}
if ($function->isEmpty()) {
throw new Exception(Exception::FUNCTION_NOT_FOUND);
}
$enabled ??= $function->getAttribute('enabled', true);
$vcsRepoId = null;
$needToDeploy = false;
//if repo id was previously empty and non empty now, we need to create a new deployment for this function
$prevVcsRepoId = $function->getAttribute('vcsRepoId', '');
if (empty($prevVcsRepoId) && !empty($repositoryId)) {
$needToDeploy = true;
}
// activate the deployment for first run of a VCS repo
if ($needToDeploy) {
$deploymentId = ID::unique();
$entrypoint = 'index.js'; //TODO: Read from function settings
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
//Add document in VCS repos collection
$vcs_repos = new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'vcsInstallationId' => $installation->getId(),
'vcsInstallationInternalId' => $installation->getInternalId(),
'projectId' => $project->getId(),
'projectInternalId' => $project->getInternalId(),
'repositoryId' => $repositoryId,
'repositoryOwner' => $repositoryOwner,
'resourceId' => $functionId,
'resourceType' => "function"
]);
$vcs_repos = $dbForConsole->createDocument('vcs_repos', $vcs_repos);
$vcsRepoId = $vcs_repos->getId();
$vcsRepoInternalId = $vcs_repos->getInternalId();
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceId' => $function->getId(),
'resourceType' => 'functions',
'entrypoint' => $entrypoint,
'type' => "vcs",
'vcsInstallationId' => $installation->getId(),
'vcsInstallationInternalId' => $installation->getInternalId(),
'vcsRepoId' => $vcsRepoId,
'vcsRepoInternalId' => $vcsRepoInternalId,
'branch' => "main",
'search' => implode(' ', [$deploymentId, $entrypoint]),
'activate' => true,
]));
}
// Disconnect repo
if (!empty($prevVcsRepoId) && empty($repositoryId)) {
$dbForConsole->deleteDocument('vcs_repos', $prevVcsRepoId);
$vcsRepoId = '';
$vcsRepoInternalId = '';
}
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
'execute' => $execute,
'name' => $name,
@ -549,6 +638,10 @@ App::put('/v1/functions/:functionId')
'entrypoint' => $entrypoint,
'buildCommand' => $buildCommand,
'installCommand' => $installCommand,
'vcsInstallationId' => $installation->getId(),
'vcsInstallationInternalId' => $installation->getInternalId(),
'vcsRepoId' => $vcsRepoId,
'vcsRepoInternalId' => $vcsRepoInternalId,
'search' => implode(' ', [$functionId, $name, $function->getAttribute('runtime')]),
])));
@ -563,6 +656,18 @@ App::put('/v1/functions/:functionId')
$eventsInstance->setParam('functionId', $function->getId());
if ($needToDeploy) {
$buildEvent = new Build();
$buildEvent
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setProject($project)
->trigger();
//TODO: Add event?
}
$response->dynamic($function, Response::MODEL_FUNCTION);
});

481
app/controllers/api/vcs.php Normal file
View file

@ -0,0 +1,481 @@
<?php
use Utopia\App;
use Appwrite\Event\Build;
use Appwrite\Event\Delete;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\Validator\Text;
use Utopia\VCS\Adapter\Git\GitHub;
use Utopia\Database\Helpers\ID;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Database\Validator\Queries\Installations;
use Utopia\Cache\Adapter\Redis;
use Utopia\Cache\Cache;
use Utopia\Database\Query;
use Utopia\Database\Adapter\MariaDB;
use Utopia\Database\Validator\Authorization;
App::get('/v1/vcs/github/installations')
->desc('Install GitHub App')
->groups(['api', 'vcs'])
->label('scope', 'public')
->label('origin', '*')
->label('sdk.auth', [])
->label('sdk.namespace', 'vcs')
->label('sdk.method', 'createGitHubInstallation')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_MOVED_PERMANENTLY)
->label('sdk.response.type', Response::CONTENT_TYPE_HTML)
->label('sdk.methodType', 'webAuth')
->inject('response')
->inject('project')
->action(function (Response $response, Document $project) {
$projectId = $project->getId();
$appName = App::getEnv('VCS_GITHUB_APP_NAME');
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->redirect("https://github.com/apps/$appName/installations/new?state=$projectId");
});
App::get('/v1/vcs/github/incominginstallation')
->desc('Capture installation id and state after GitHub App Installation')
->groups(['api', 'vcs'])
->label('scope', 'public')
->param('installation_id', '', new Text(256), 'installation_id')
->param('setup_action', '', new Text(256), 'setup_action')
->param('state', '', new Text(256), 'state')
->inject('request')
->inject('response')
->inject('dbForConsole')
->action(function (string $installationId, string $setupAction, string $state, Request $request, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $state);
if ($project->isEmpty()) {
$url = $request->getProtocol() . '://' . $request->getHostname() . "/";
$response->redirect($url);
}
$projectInternalId = $project->getInternalId();
$vcsInstallation = $dbForConsole->findOne('vcs_installations', [
Query::equal('installationId', [$installationId]),
Query::equal('projectInternalId', [$projectInternalId])
]);
if (!$vcsInstallation) {
$vcsInstallation = new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'installationId' => $installationId,
'projectId' => $state,
'projectInternalId' => $projectInternalId,
'provider' => 'GitHub',
'organization' => '(todo) My Awesome Organization',
'accessToken' => null
]);
$vcsInstallation = $dbForConsole->createDocument('vcs_installations', $vcsInstallation);
} else {
$vcsInstallation = $vcsInstallation->setAttribute('organization', '(todo) My Awesome Organization');
$vcsInstallation = $dbForConsole->updateDocument('vcs_installations', $vcsInstallation->getId(), $vcsInstallation);
}
$url = $request->getProtocol() . '://' . $request->getHostname() . ":3000/console/project-$state/settings/git-installations";
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->redirect($url);
});
App::get('v1/vcs/github/installations/:installationId/repositories')
->desc('List repositories')
->groups(['api', 'vcs'])
->label('scope', 'public')
->label('sdk.namespace', 'vcs')
->label('sdk.method', 'listRepositories')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_REPOSITORY_LIST)
->param('installationId', '', new Text(256), 'Installation Id')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
->inject('project')
->inject('dbForConsole')
->action(function (string $vcsInstallationId, string $search, Response $response, Document $project, Database $dbForConsole) {
if (empty($search)) {
$search = "";
}
$installation = $dbForConsole->getDocument('vcs_installations', $vcsInstallationId, [
Query::equal('projectInternalId', [$project->getInternalId()])
]);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
$installationId = $installation->getAttribute('installationId');
$privateKey = App::getEnv('VCS_GITHUB_PRIVATE_KEY');
$githubAppId = App::getEnv('VCS_GITHUB_APP_ID');
$github = new GitHub();
$github->initialiseVariables($installationId, $privateKey, $githubAppId);
$page = 1;
$per_page = 100; // max limit of GitHub API
$repos = []; // Array to store all repositories
// loop to store all repos in repos array
do {
$repositories = $github->listRepositoriesForGitHubApp($page, $per_page);
$repos = array_merge($repos, $repositories);
$page++;
} while ($repositories == $per_page);
// Filter repositories based on search parameter
if (!empty($search)) {
$repos = array_filter($repos, function ($repo) use ($search) {
$repoName = strtolower($repo['name']);
$searchTerm = strtolower($search);
return strpos($repoName, $searchTerm) !== false;
});
}
// Sort repositories by last modified date in descending order
usort($repos, function ($repo1, $repo2) {
return strtotime($repo2['pushed_at']) - strtotime($repo1['pushed_at']);
});
// Limit the maximum results to 5
$repos = array_slice($repos, 0, 5);
$response->dynamic(new Document([
'repositories' => $repos,
'total' => \count($repos),
]), Response::MODEL_REPOSITORY_LIST);
});
App::post('/v1/vcs/github/incomingwebhook')
->desc('Captures GitHub Webhook Events')
->groups(['api', 'vcs'])
->label('scope', 'public')
->inject('request')
->inject('response')
->inject('dbForConsole')
->inject('cache')
->inject('db')
->action(
function (Request $request, Response $response, Database $dbForConsole, mixed $cache, mixed $db) {
$cache = new Cache(new Redis($cache));
$event = $request->getHeader('x-github-event', '');
$payload = $request->getRawPayload();
$github = new GitHub();
$privateKey = App::getEnv('VCS_GITHUB_PRIVATE_KEY');
$githubAppId = App::getEnv('VCS_GITHUB_APP_ID');
$parsedPayload = $github->parseWebhookEventPayload($event, $payload);
if ($event == $github::EVENT_PUSH) {
$branchName = $parsedPayload["branch"];
$repositoryId = $parsedPayload["repositoryId"];
$installationId = $parsedPayload["installationId"];
$SHA = $parsedPayload["SHA"];
$owner = $parsedPayload["owner"];
//find functionId from functions table
$resources = $dbForConsole->find('vcs_repos', [
Query::equal('repositoryId', [$repositoryId]),
Query::limit(100),
]);
foreach ($resources as $resource) {
$resourceType = $resource->getAttribute('resourceType');
if ($resourceType == "function") {
// TODO: For cloud, we might have different $db
$dbForProject = new Database(new MariaDB($db), $cache);
$dbForProject->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$dbForProject->setNamespace("_{$resource->getAttribute('projectInternalId')}");
$functionId = $resource->getAttribute('resourceId');
//TODO: Why is Authorization::skip needed?
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
$projectId = $resource->getAttribute('projectId');
//TODO: Why is Authorization::skip needed?
$project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
$deploymentId = ID::unique();
$entrypoint = 'index.js'; //TODO: Read from function settings
$vcsRepoId = $resource->getId();
$vcsRepoInternalId = $resource->getInternalId();
$vcsInstallationId = $resource->getAttribute('vcsInstallationId');
$vcsInstallationInternalId = $resource->getAttribute('vcsInstallationInternalId');
$activate = false;
if ($branchName == "main") {
$activate = true;
}
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceId' => $functionId,
'resourceType' => 'functions',
'entrypoint' => $entrypoint,
'type' => "vcs",
'vcsInstallationId' => $vcsInstallationId,
'vcsInstallationInternalId' => $vcsInstallationInternalId,
'vcsRepoId' => $vcsRepoId,
'vcsRepoInternalId' => $vcsRepoInternalId,
'branch' => $branchName,
'search' => implode(' ', [$deploymentId, $entrypoint]),
'activate' => $activate,
]));
$targetUrl = $request->getProtocol() . '://' . $request->getHostname() . ":3000/console/project-$projectId/functions/function-$functionId";
$buildEvent = new Build();
$buildEvent
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setProject($project)
->setSHA($SHA)
->setOwner($owner)
->setTargetUrl($targetUrl)
->trigger();
//TODO: Add event?
}
}
} elseif ($event == $github::EVENT_INSTALLATION) {
if ($parsedPayload["action"] == "deleted") {
// TODO: Use worker for this job instead
$installationId = $parsedPayload["installationId"];
$vcsInstallations = $dbForConsole->find('vcs_installations', [
Query::equal('installationId', [$installationId]),
Query::limit(1000)
]);
foreach ($vcsInstallations as $installation) {
$vcsRepos = $dbForConsole->find('vcs_repos', [
Query::equal('vcsInstallationId', [$installation->getId()]),
Query::limit(1000)
]);
foreach ($vcsRepos as $repo) {
$dbForConsole->deleteDocument('vcs_repos', $repo->getId());
}
$dbForConsole->deleteDocument('vcs_installations', $installation->getId());
}
}
} elseif ($event == $github::EVENT_PULL_REQUEST) {
if ($parsedPayload["action"] == "opened" or $parsedPayload["action"] == "reopened") {
$startNewDeployment = false;
$branchName = $parsedPayload["branch"];
$repositoryId = $parsedPayload["repositoryId"];
$installationId = $parsedPayload["installationId"];
$pullRequestNumber = $parsedPayload["pullRequestNumber"];
$repositoryName = $parsedPayload["repositoryName"];
$owner = $parsedPayload["owner"];
$github->initialiseVariables($installationId, $privateKey, $githubAppId);
$vcsRepos = $dbForConsole->find('vcs_repos', [
Query::equal('repositoryId', [$repositoryId]),
Query::orderDesc('$createdAt')
]);
$dbForProject = new Database(new MariaDB($db), $cache);
$dbForProject->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
if ($vcsRepos) {
$dbForProject->setNamespace("_{$vcsRepos[0]->getAttribute('projectInternalId')}");
$vcsRepoId = $vcsRepos[0]->getId();
$deployment = Authorization::skip(fn () => $dbForProject->find('deployments', [
Query::equal('vcsRepoId', [$vcsRepoId]),
Query::equal('branch', [$branchName]),
Query::orderDesc('$createdAt')
]));
if ($deployment) {
$buildId = $deployment[0]->getAttribute('buildId');
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $buildId));
$buildStatus = $build->getAttribute('status');
$comment = "| Build Status |\r\n | --------------- |\r\n | $buildStatus |";
$commentId = $github->addComment($owner, $repositoryName, $pullRequestNumber, $comment);
} else {
$startNewDeployment = true;
}
} else {
$startNewDeployment = true;
}
if ($startNewDeployment) {
$commentId = strval($github->addComment($owner, $repositoryName, $pullRequestNumber, "Build is not deployed yet 🚀"));
foreach ($vcsRepos as $resource) {
$resourceType = $resource->getAttribute('resourceType');
if ($resourceType == "function") {
// TODO: For cloud, we might have different $db
$dbForProject->setNamespace("_{$resource->getAttribute('projectInternalId')}");
$functionId = $resource->getAttribute('resourceId');
//TODO: Why is Authorization::skip needed?
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
$projectId = $resource->getAttribute('projectId');
//TODO: Why is Authorization::skip needed?
$project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
$deploymentId = ID::unique();
$entrypoint = 'index.js'; //TODO: Read from function settings
$vcsRepoId = $resource->getId();
$vcsRepoInternalId = $resource->getInternalId();
$vcsInstallationId = $resource->getAttribute('vcsInstallationId');
$vcsInstallationInternalId = $resource->getAttribute('vcsInstallationInternalId');
$activate = false;
if ($branchName == "main") {
$activate = true;
}
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceId' => $functionId,
'resourceType' => 'functions',
'entrypoint' => $entrypoint,
'type' => "vcs",
'vcsInstallationId' => $vcsInstallationId,
'vcsInstallationInternalId' => $vcsInstallationInternalId,
'vcsRepoId' => $vcsRepoId,
'vcsRepoInternalId' => $vcsRepoInternalId,
'branch' => $branchName,
'vcsCommentId' => $commentId,
'search' => implode(' ', [$deploymentId, $entrypoint]),
'activate' => $activate,
]));
$buildEvent = new Build();
$buildEvent
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setProject($project)
->setOwner($owner)
->trigger();
//TODO: Add event?
}
}
}
}
}
$response->json($parsedPayload);
}
);
App::get('/v1/vcs/installations')
->groups(['api', 'vcs'])
->desc('List installations')
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'vcs')
->label('sdk.method', 'listInstallations')
->label('sdk.description', '/docs/references/vcs/list-installations.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_INSTALLATION_LIST)
->param('queries', [], new Installations(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Installations::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
->inject('project')
->inject('dbForConsole')
->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForConsole) {
$queries = Query::parseQueries($queries);
$queries[] = Query::equal('projectInternalId', [$project->getInternalId()]);
if (!empty($search)) {
$queries[] = Query::search('search', $search);
}
// Get cursor document if there was a cursor query
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE);
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$vcsInstallationId = $cursor->getValue();
$cursorDocument = $dbForConsole->getDocument('vcs_installations', $vcsInstallationId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Installation '{$vcsInstallationId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
$response->dynamic(new Document([
'installations' => $dbForConsole->find('vcs_installations', $queries),
'total' => $dbForConsole->count('vcs_installations', $filterQueries, APP_LIMIT_COUNT),
]), Response::MODEL_INSTALLATION_LIST);
});
App::delete('/v1/vcs/installations/:installationId')
->groups(['api', 'vcs'])
->desc('Delete Installation')
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'vcs')
->label('sdk.method', 'deleteInstallation')
->label('sdk.description', '/docs/references/vcs/delete-installation.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('installationId', '', new Text(256), 'Installation Id')
->inject('response')
->inject('project')
->inject('dbForConsole')
->inject('deletes')
->action(function (string $vcsInstallationId, Response $response, Document $project, Database $dbForConsole, Delete $deletes) {
$installation = $dbForConsole->getDocument('vcs_installations', $vcsInstallationId, [
Query::equal('projectInternalId', [$project->getInternalId()])
]);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
if (!$dbForConsole->deleteDocument('vcs_installations', $installation->getId())) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove installation from DB');
}
$deletes
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($installation);
$response->noContent();
});

View file

@ -152,6 +152,7 @@ const DELETE_TYPE_ABUSE = 'abuse';
const DELETE_TYPE_USAGE = 'usage';
const DELETE_TYPE_REALTIME = 'realtime';
const DELETE_TYPE_BUCKETS = 'buckets';
const DELETE_TYPE_INSTALLATIONS = 'vcs_installations';
const DELETE_TYPE_RULES = 'rules';
const DELETE_TYPE_SESSIONS = 'sessions';
const DELETE_TYPE_CACHE_BY_TIMESTAMP = 'cacheByTimeStamp';

View file

@ -16,6 +16,8 @@ use Utopia\Database\Document;
use Utopia\Config\Config;
use Utopia\Database\Validator\Authorization;
use Utopia\Storage\Storage;
use Utopia\Database\Validator\Authorization;
use Utopia\VCS\Adapter\Git\GitHub;
require_once __DIR__ . '/../init.php';
@ -43,12 +45,15 @@ class BuildsV1 extends Worker
$project = new Document($this->args['project'] ?? []);
$resource = new Document($this->args['resource'] ?? []);
$deployment = new Document($this->args['deployment'] ?? []);
$SHA = $this->args['SHA'] ?? '';
$owner = $this->args['owner'] ?? '';
$targetUrl = $this->args['targetUrl'] ?? '';
switch ($type) {
case BUILD_TYPE_DEPLOYMENT:
case BUILD_TYPE_RETRY:
Console::info('Creating build for deployment: ' . $deployment->getId());
$this->buildDeployment($project, $resource, $deployment);
$this->buildDeployment($project, $resource, $deployment, $SHA, $owner, $targetUrl);
break;
default:
@ -57,11 +62,12 @@ class BuildsV1 extends Worker
}
}
protected function buildDeployment(Document $project, Document $function, Document $deployment)
protected function buildDeployment(Document $project, Document $function, Document $deployment, string $SHA = '', string $owner = '', string $targetUrl = '')
{
global $register;
$dbForProject = $this->getProjectDB($project);
$dbForConsole = $this->getConsoleDB();
$function = $dbForProject->getDocument('functions', $function->getId());
if ($function->isEmpty()) {
@ -80,7 +86,8 @@ class BuildsV1 extends Worker
throw new Exception('Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
}
$connection = App::getEnv('_APP_CONNECTIONS_STORAGE', ''); /** @TODO : move this to the registry or someplace else */
$connection = App::getEnv('_APP_CONNECTIONS_STORAGE', '');
/** @TODO : move this to the registry or someplace else */
$device = Storage::DEVICE_LOCAL;
try {
$dsn = new DSN($connection);
@ -94,6 +101,94 @@ class BuildsV1 extends Worker
$durationStart = \microtime(true);
if (empty($buildId)) {
$buildId = ID::unique();
$vcsInstallationId = $deployment->getAttribute('vcsInstallationId');
$vcsRepoId = $deployment->getAttribute('vcsRepoId');
$isVcsEnabled = $vcsRepoId !== null ? true : false;
$addComment = false;
if ($isVcsEnabled) {
$vcsRepos = Authorization::skip(fn () => $dbForConsole
->getDocument('vcs_repos', $vcsRepoId));
$repositoryId = $vcsRepos->getAttribute('repositoryId');
$owner = $vcsRepos->getAttribute('repositoryOwner');
$vcsInstallations = Authorization::skip(fn () => $dbForConsole
->getDocument('vcs_installations', $vcsInstallationId));
$installationId = $vcsInstallations->getAttribute('installationId');
$privateKey = App::getEnv('VCS_GITHUB_PRIVATE_KEY');
$githubAppId = App::getEnv('VCS_GITHUB_APP_ID');
$github = new GitHub();
$github->initialiseVariables($installationId, $privateKey, $githubAppId);
$repositoryName = $github->getRepositoryName($repositoryId);
$branchName = $deployment->getAttribute('branch');
$gitCloneCommand = $github->generateGitCloneCommand($owner, $repositoryId, $branchName);
$stdout = '';
$stderr = '';
Console::execute('mkdir /tmp/builds/' . $buildId, '', $stdout, $stderr);
Console::execute($gitCloneCommand . ' /tmp/builds/' . $buildId . '/code', '', $stdout, $stderr);
Console::execute('tar --exclude code.tar.gz -czf /tmp/builds/' . $buildId . '/code.tar.gz -C /tmp/builds/' . $buildId . '/code .', '', $stdout, $stderr);
$deviceFunctions = $this->getFunctionsDevice($project->getId());
$fileName = 'code.tar.gz';
$fileTmpName = '/tmp/builds/' . $buildId . '/code.tar.gz';
$deploymentId = $deployment->getId();
$path = $deviceFunctions->getPath($deploymentId . '.' . \pathinfo($fileName, PATHINFO_EXTENSION));
$result = $deviceFunctions->move($fileTmpName, $path);
if (!$result) {
throw new \Exception("Unable to move file");
}
Console::execute('rm -rf /tmp/builds/' . $buildId, '', $stdout, $stderr);
$build = $dbForProject->createDocument('builds', new Document([
'$id' => $buildId,
'$permissions' => [],
'startTime' => $startTime,
'deploymentId' => $deployment->getId(),
'status' => 'processing',
'outputPath' => '',
'runtime' => $function->getAttribute('runtime'),
'source' => $path,
'sourceType' => strtolower(App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL)),
'stdout' => '',
'stderr' => '',
'endTime' => null,
'duration' => 0
]));
if ($SHA !== "" && $owner !== "") {
$github->updateCommitStatus($repositoryName, $SHA, $owner, "pending", "Deployment is being processed..", $targetUrl, "Appwrite Deployment");
}
$commentId = $deployment->getAttribute('vcsCommentId');
if ($commentId) {
$comment = "| Build Status |\r\n | --------------- |\r\n | Processing |";
$github->updateComment($owner, $repositoryName, $commentId, $comment);
$addComment = true;
}
} else {
$build = $dbForProject->createDocument('builds', new Document([
'$id' => $buildId,
'$permissions' => [],
'startTime' => $startTime,
'deploymentId' => $deployment->getId(),
'status' => 'processing',
'outputPath' => '',
'runtime' => $function->getAttribute('runtime'),
'source' => $deployment->getAttribute('path'),
'sourceType' => strtolower(App::getEnv('_APP_STORAGE_DEVICE', Storage::DEVICE_LOCAL)),
'stdout' => '',
'stderr' => '',
'endTime' => null,
'duration' => 0
]));
}
$build = $dbForProject->createDocument('builds', new Document([
'$id' => $buildId,
'$permissions' => [],
@ -122,6 +217,14 @@ class BuildsV1 extends Worker
$build->setAttribute('status', 'building');
$build = $dbForProject->updateDocument('builds', $buildId, $build);
if ($isVcsEnabled) {
$commentId = $deployment->getAttribute('vcsCommentId');
if ($commentId) {
$comment = "| Build Status |\r\n | --------------- |\r\n | Building |";
$github->updateComment($owner, $repositoryName, $commentId, $comment);
}
}
/** Trigger Webhook */
$deploymentModel = new Deployment();
@ -167,6 +270,10 @@ class BuildsV1 extends Worker
$source = $deployment->getAttribute('path');
if ($isVcsEnabled) {
$source = $path;
}
$vars = array_reduce($function->getAttribute('vars', []), function (array $carry, Document $var) {
$carry[$var->getAttribute('key')] = $var->getAttribute('value');
return $carry;
@ -214,6 +321,22 @@ class BuildsV1 extends Worker
$build->setAttribute('stderr', $response['stderr']);
$build->setAttribute('stdout', $response['stdout']);
if ($isVcsEnabled) {
$status = $response["status"];
if ($status === "ready" && $SHA !== "" && $owner !== "") {
$github->updateCommitStatus($repositoryName, $SHA, $owner, "success", "Deployment is successful!", $targetUrl, "Appwrite Deployment");
} elseif ($status === "failed" && $SHA !== "" && $owner !== "") {
$github->updateCommitStatus($repositoryName, $SHA, $owner, "failure", "Deployment failed.", $targetUrl, "Appwrite Deployment");
}
$commentId = $deployment->getAttribute('vcsCommentId');
if ($commentId) {
$comment = "| Build Status |\r\n | --------------- |\r\n | $status |";
$github->updateComment($owner, $repositoryName, $commentId, $comment);
}
}
/* Also update the deployment buildTime */
$deployment->setAttribute('buildTime', $response['duration']);

View file

@ -64,6 +64,10 @@ class DeletesV1 extends Worker
break;
case DELETE_TYPE_BUCKETS:
$this->deleteBucket($document, $project);
break;
case DELETE_TYPE_INSTALLATIONS:
$this->deleteInstallation($document, $project);
break;
case DELETE_TYPE_RULES:
$this->deleteRule($document, $project);
break;
@ -329,7 +333,7 @@ class DeletesV1 extends Worker
'teams',
$teamId,
// Ensure that total >= 0
$team->setAttribute('total', \max($team->getAttribute('total', 0) - 1, 0))
$team->setAttribute('total', \max($team->getAttribute('total', 0) - 1, 0))
);
}
}
@ -626,6 +630,43 @@ class DeletesV1 extends Worker
Console::info("Found {$count} projects " . ($executionEnd - $executionStart) . " seconds");
}
/**
* @param string $collection collectionID
* @param Query[] $queries
* @param Database $database
* @param callable $callback
*/
protected function listByGroup(string $collection, array $queries, Database $database, callable $callback = null): void
{
$count = 0;
$chunk = 0;
$limit = 50;
$results = [];
$sum = $limit;
$executionStart = \microtime(true);
while ($sum === $limit) {
$chunk++;
$results = $database->find($collection, \array_merge([Query::limit($limit)], $queries));
$sum = count($results);
foreach ($results as $document) {
if (is_callable($callback)) {
$callback($document);
}
$count++;
}
}
$executionEnd = \microtime(true);
Console::info("Listed {$count} document by group in " . ($executionEnd - $executionStart) . " seconds");
}
/**
* @param string $collection collectionID
* @param Query[] $queries
@ -737,6 +778,25 @@ class DeletesV1 extends Worker
$device->deletePath($document->getId());
}
protected function deleteInstallation(Document $document, Document $project)
{
$dbForProject = $this->getProjectDB($projectId);
$dbForConsole = $this->getConsoleDB();
$this->listByGroup('functions', [
Query::equal('vcsInstallationInternalId', [$document->getInternalId()])
], $dbForProject, function ($function) use ($dbForProject, $dbForConsole) {
$dbForConsole->deleteDocument('vcs_repos', $function->getAttribute('vcsRepoId'));
$function = $function
->setAttribute('vcsInstallationId', '')
->setAttribute('vcsInstallationInternalId', '')
->setAttribute('vcsRepoId', '')
->setAttribute('vcsRepoInternalId', '');
$dbForProject->updateDocument('functions', $function->getId(), $function);
});
}
protected function deleteRuntimes(?Document $function, Document $project)
{
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));

View file

@ -65,6 +65,7 @@
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.13.*",
"utopia-php/swoole": "0.5.*",
"utopia-php/vcs": "dev-feat-git-adapter as 0.1.99",
"utopia-php/websocket": "0.1.0",
"resque/php-resque": "1.3.6",
"matomo/device-detector": "6.0.0",
@ -79,6 +80,10 @@
{
"url": "https://github.com/appwrite/runtimes.git",
"type": "git"
},
{
"url": "https://github.com/utopia-php/vcs.git",
"type": "git"
}
],
"require-dev": {

View file

@ -177,6 +177,9 @@ services:
- _APP_REGION
- _APP_CONSOLE_GITHUB_APP_ID
- _APP_CONSOLE_GITHUB_SECRET
- VCS_GITHUB_APP_NAME
- VCS_GITHUB_PRIVATE_KEY
- VCS_GITHUB_APP_ID
appwrite-realtime:
entrypoint: realtime
@ -382,7 +385,9 @@ services:
image: appwrite-dev
networks:
- appwrite
volumes:
volumes:
- appwrite-functions:/storage/functions:rw
- appwrite-builds:/storage/builds:rw
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
@ -412,6 +417,9 @@ services:
- _APP_CONNECTIONS_STORAGE
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
- VCS_GITHUB_APP_NAME
- VCS_GITHUB_PRIVATE_KEY
- VCS_GITHUB_APP_ID
appwrite-worker-certificates:
entrypoint: worker-certificates

View file

@ -10,12 +10,54 @@ class Build extends Event
protected string $type = '';
protected ?Document $resource = null;
protected ?Document $deployment = null;
protected string $SHA = '';
protected string $owner = '';
protected string $targetUrl = '';
public function __construct()
{
parent::__construct(Event::BUILDS_QUEUE_NAME, Event::BUILDS_CLASS_NAME);
}
/**
* Sets commit SHA for the build event.
*
* @param string $SHA is the commit hash of the incoming commit
* @return self
*/
public function setSHA(string $SHA): self
{
$this->SHA = $SHA;
return $this;
}
/**
* Sets repository owner name for the build event.
*
* @param string $owner is the name of the repository owner
* @return self
*/
public function setOwner(string $owner): self
{
$this->owner = $owner;
return $this;
}
/**
* Sets redirect target url for the deployment
*
* @param string $targetUrl is the url that is to be set
* @return self
*/
public function setTargetUrl(string $targetUrl): self
{
$this->targetUrl = $targetUrl;
return $this;
}
/**
* Sets resource document for the build event.
*
@ -97,7 +139,10 @@ class Build extends Event
'project' => $this->project,
'resource' => $this->resource,
'deployment' => $this->deployment,
'type' => $this->type
'type' => $this->type,
'SHA' => $this->SHA,
'owner' => $this->owner,
'targetUrl' => $this->targetUrl
]);
}
}

View file

@ -106,6 +106,9 @@ class Exception extends \Exception
public const STORAGE_INVALID_CONTENT_RANGE = 'storage_invalid_content_range';
public const STORAGE_INVALID_RANGE = 'storage_invalid_range';
/** VCS */
public const INSTALLATION_NOT_FOUND = 'installation_not_found';
/** Functions */
public const FUNCTION_NOT_FOUND = 'function_not_found';
public const FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported';

View file

@ -334,6 +334,7 @@ class OpenAPI3 extends Format
case 'Appwrite\Utopia\Database\Validator\Queries\Collections':
case 'Appwrite\Utopia\Database\Validator\Queries\Databases':
case 'Appwrite\Utopia\Database\Validator\Queries\Deployments':
case 'Appwrite\Utopia\Database\Validator\Queries\Installations':
case 'Appwrite\Utopia\Database\Validator\Queries\Documents':
case 'Appwrite\Utopia\Database\Validator\Queries\Executions':
case 'Appwrite\Utopia\Database\Validator\Queries\Files':

View file

@ -328,6 +328,7 @@ class Swagger2 extends Format
case 'Appwrite\Utopia\Database\Validator\Queries\Collections':
case 'Appwrite\Utopia\Database\Validator\Queries\Databases':
case 'Appwrite\Utopia\Database\Validator\Queries\Deployments':
case 'Appwrite\Utopia\Database\Validator\Queries\Installations':
case 'Appwrite\Utopia\Database\Validator\Queries\Documents':
case 'Appwrite\Utopia\Database\Validator\Queries\Executions':
case 'Appwrite\Utopia\Database\Validator\Queries\Files':

View file

@ -0,0 +1,20 @@
<?php
namespace Appwrite\Utopia\Database\Validator\Queries;
class Installations extends Base
{
public const ALLOWED_ATTRIBUTES = [
'provider',
'organization'
];
/**
* Expression constructor
*
*/
public function __construct()
{
parent::__construct('vcs_installations', self::ALLOWED_ATTRIBUTES);
}
}

View file

@ -70,6 +70,8 @@ use Appwrite\Utopia\Response\Model\HealthQueue;
use Appwrite\Utopia\Response\Model\HealthStatus;
use Appwrite\Utopia\Response\Model\HealthTime;
use Appwrite\Utopia\Response\Model\HealthVersion;
use Appwrite\Utopia\Response\Model\Installation;
use Appwrite\Utopia\Response\Model\Repository;
use Appwrite\Utopia\Response\Model\Mock; // Keep last
use Appwrite\Utopia\Response\Model\Provider;
use Appwrite\Utopia\Response\Model\Runtime;
@ -176,6 +178,12 @@ class Response extends SwooleResponse
public const MODEL_MEMBERSHIP = 'membership';
public const MODEL_MEMBERSHIP_LIST = 'membershipList';
// VCS
public const MODEL_INSTALLATION = 'installation';
public const MODEL_INSTALLATION_LIST = 'installationList';
public const MODEL_REPOSITORY = 'repository';
public const MODEL_REPOSITORY_LIST = 'repositoryList';
// Functions
public const MODEL_FUNCTION = 'function';
public const MODEL_FUNCTION_LIST = 'functionList';
@ -207,6 +215,7 @@ class Response extends SwooleResponse
public const MODEL_PLATFORM_LIST = 'platformList';
public const MODEL_VARIABLE = 'variable';
public const MODEL_VARIABLE_LIST = 'variableList';
public const MODEL_VCS = 'vcs';
// Health
public const MODEL_HEALTH_STATUS = 'healthStatus';
@ -260,6 +269,8 @@ class Response extends SwooleResponse
->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('Functions List', self::MODEL_FUNCTION_LIST, 'functions', self::MODEL_FUNCTION))
->setModel(new BaseList('Installations List', self::MODEL_INSTALLATION_LIST, 'installations', self::MODEL_INSTALLATION))
->setModel(new BaseList('Repositories List', self::MODEL_REPOSITORY_LIST, 'repositories', self::MODEL_REPOSITORY))
->setModel(new BaseList('Runtimes List', self::MODEL_RUNTIME_LIST, 'runtimes', self::MODEL_RUNTIME))
->setModel(new BaseList('Deployments List', self::MODEL_DEPLOYMENT_LIST, 'deployments', self::MODEL_DEPLOYMENT))
->setModel(new BaseList('Executions List', self::MODEL_EXECUTION_LIST, 'executions', self::MODEL_EXECUTION))
@ -314,6 +325,8 @@ class Response extends SwooleResponse
->setModel(new Team())
->setModel(new Membership())
->setModel(new Func())
->setModel(new Installation())
->setModel(new Repository())
->setModel(new Runtime())
->setModel(new Deployment())
->setModel(new Execution())

View file

@ -111,6 +111,18 @@ class Func extends Model
'default' => '',
'example' => 'npm install',
])
->addRule('repositoryId', [
'type' => self::TYPE_STRING,
'description' => 'VCS Repository ID',
'default' => false,
'example' => '35493993',
])
->addRule('vcsInstallationId', [
'type' => self::TYPE_STRING,
'description' => 'Function vcs installation id.',
'default' => '',
'example' => '644051bd6572792165cc',
])
;
}

View file

@ -0,0 +1,72 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Installation extends Model
{
public function __construct()
{
$this
->addRule('$id', [
'type' => self::TYPE_STRING,
'description' => 'Function ID.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('$createdAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Function creation date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('$updatedAt', [
'type' => self::TYPE_DATETIME,
'description' => 'Function update date in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE,
])
->addRule('provider', [
'type' => self::TYPE_STRING,
'description' => 'Installation provider.',
'default' => [],
'example' => 'github',
'array' => false,
])
->addRule('organization', [
'type' => self::TYPE_STRING,
'description' => 'Installation organization.',
'default' => [],
'example' => 'appwrite',
'array' => false,
])
->addRule('installationId', [
'type' => self::TYPE_STRING,
'description' => 'Provider installation ID.',
'default' => '',
'example' => '5322',
]);
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Installation';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_INSTALLATION;
}
}

View file

@ -0,0 +1,52 @@
<?php
namespace Appwrite\Utopia\Response\Model;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
class Repository extends Model
{
public function __construct()
{
$this
->addRule('id', [
'type' => self::TYPE_INTEGER,
'description' => 'Repository ID.',
'default' => '',
'example' => '5e5ea5c16897e',
])
->addRule('name', [
'type' => self::TYPE_STRING,
'description' => 'Repository Name.',
'default' => '',
'example' => 'appwrite',
])
->addRule('owner', [
'type' => self::TYPE_JSON,
'description' => 'Repository Owner.',
'default' => '',
'example' => '{"login": "Example Owner"}',
]);
}
/**
* Get Name
*
* @return string
*/
public function getName(): string
{
return 'Repository';
}
/**
* Get Type
*
* @return string
*/
public function getType(): string
{
return Response::MODEL_REPOSITORY;
}
}