Copy Khushboo's integration from feat-peach-q1-kh
This commit is contained in:
parent
6766508c95
commit
6e515e3cc4
5
.env
5
.env
|
@ -80,4 +80,7 @@ _APP_REGION=default
|
||||||
_APP_DOCKER_HUB_USERNAME=
|
_APP_DOCKER_HUB_USERNAME=
|
||||||
_APP_DOCKER_HUB_PASSWORD=
|
_APP_DOCKER_HUB_PASSWORD=
|
||||||
_APP_CONSOLE_GITHUB_SECRET=
|
_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=
|
|
@ -1007,7 +1007,7 @@ $collections = [
|
||||||
[
|
[
|
||||||
'$id' => ID::custom('_key_region_resourceType_resourceUpdatedAt'),
|
'$id' => ID::custom('_key_region_resourceType_resourceUpdatedAt'),
|
||||||
'type' => Database::INDEX_KEY,
|
'type' => Database::INDEX_KEY,
|
||||||
'attributes' => ['region', 'resourceType','resourceUpdatedAt'],
|
'attributes' => ['region', 'resourceType', 'resourceUpdatedAt'],
|
||||||
'lengths' => [],
|
'lengths' => [],
|
||||||
'orders' => [],
|
'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' => [
|
'functions' => [
|
||||||
'$collection' => ID::custom(Database::METADATA),
|
'$collection' => ID::custom(Database::METADATA),
|
||||||
'$id' => ID::custom('functions'),
|
'$id' => ID::custom('functions'),
|
||||||
|
@ -2258,6 +2430,48 @@ $collections = [
|
||||||
'required' => true,
|
'required' => true,
|
||||||
'array' => false,
|
'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'),
|
'$id' => ID::custom('logging'),
|
||||||
'type' => Database::VAR_BOOLEAN,
|
'type' => Database::VAR_BOOLEAN,
|
||||||
|
@ -2445,6 +2659,20 @@ $collections = [
|
||||||
'lengths' => [],
|
'lengths' => [],
|
||||||
'orders' => [Database::ORDER_ASC],
|
'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'),
|
'$id' => ID::custom('_key_runtime'),
|
||||||
'type' => Database::INDEX_KEY,
|
'type' => Database::INDEX_KEY,
|
||||||
|
@ -2601,6 +2829,77 @@ $collections = [
|
||||||
'array' => false,
|
'array' => false,
|
||||||
'filters' => [],
|
'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'),
|
'$id' => ID::custom('size'),
|
||||||
'type' => Database::VAR_INTEGER,
|
'type' => Database::VAR_INTEGER,
|
||||||
|
@ -3549,7 +3848,7 @@ $collections = [
|
||||||
'array' => false,
|
'array' => false,
|
||||||
'filters' => [],
|
'filters' => [],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'indexes' => [
|
'indexes' => [
|
||||||
[
|
[
|
||||||
'$id' => '_key_accessedAt',
|
'$id' => '_key_accessedAt',
|
||||||
|
@ -3878,7 +4177,7 @@ $collections = [
|
||||||
'required' => true,
|
'required' => true,
|
||||||
'default' => null,
|
'default' => null,
|
||||||
'array' => false,
|
'array' => false,
|
||||||
'filters' => [ 'encrypt' ]
|
'filters' => ['encrypt']
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'$id' => ID::custom('search'),
|
'$id' => ID::custom('search'),
|
||||||
|
|
|
@ -324,6 +324,13 @@ return [
|
||||||
'code' => 416,
|
'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 */
|
/** Functions */
|
||||||
Exception::FUNCTION_NOT_FOUND => [
|
Exception::FUNCTION_NOT_FOUND => [
|
||||||
'name' => Exception::FUNCTION_NOT_FOUND,
|
'name' => Exception::FUNCTION_NOT_FOUND,
|
||||||
|
|
|
@ -160,6 +160,19 @@ return [
|
||||||
'optional' => true,
|
'optional' => true,
|
||||||
'icon' => '/images/services/users.png',
|
'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' => [
|
'functions' => [
|
||||||
'key' => 'functions',
|
'key' => 'functions',
|
||||||
'name' => 'Functions',
|
'name' => 'Functions',
|
||||||
|
|
|
@ -80,6 +80,7 @@ App::post('/v1/functions')
|
||||||
->inject('events')
|
->inject('events')
|
||||||
->inject('dbForConsole')
|
->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) {
|
->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;
|
$functionId = ($functionId == 'unique()') ? ID::unique() : $functionId;
|
||||||
$function = $dbForProject->createDocument('functions', new Document([
|
$function = $dbForProject->createDocument('functions', new Document([
|
||||||
|
@ -522,13 +523,17 @@ App::put('/v1/functions/:functionId')
|
||||||
->param('entrypoint', '', new Text('1028'), 'Entrypoint File.', true)
|
->param('entrypoint', '', new Text('1028'), 'Entrypoint File.', true)
|
||||||
->param('buildCommand', '', new Text('1028'), 'Build Command.', true)
|
->param('buildCommand', '', new Text('1028'), 'Build Command.', true)
|
||||||
->param('installCommand', '', new Text('1028'), 'Install 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('response')
|
||||||
->inject('dbForProject')
|
->inject('dbForProject')
|
||||||
|
->inject('dbForConsole')
|
||||||
->inject('project')
|
->inject('project')
|
||||||
->inject('user')
|
->inject('user')
|
||||||
->inject('events')
|
->inject('events')
|
||||||
->inject('dbForConsole')
|
->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);
|
$function = $dbForProject->getDocument('functions', $functionId);
|
||||||
|
|
||||||
|
@ -536,8 +541,92 @@ App::put('/v1/functions/:functionId')
|
||||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
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);
|
$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(), [
|
$function = $dbForProject->updateDocument('functions', $function->getId(), new Document(array_merge($function->getArrayCopy(), [
|
||||||
'execute' => $execute,
|
'execute' => $execute,
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
|
@ -549,6 +638,10 @@ App::put('/v1/functions/:functionId')
|
||||||
'entrypoint' => $entrypoint,
|
'entrypoint' => $entrypoint,
|
||||||
'buildCommand' => $buildCommand,
|
'buildCommand' => $buildCommand,
|
||||||
'installCommand' => $installCommand,
|
'installCommand' => $installCommand,
|
||||||
|
'vcsInstallationId' => $installation->getId(),
|
||||||
|
'vcsInstallationInternalId' => $installation->getInternalId(),
|
||||||
|
'vcsRepoId' => $vcsRepoId,
|
||||||
|
'vcsRepoInternalId' => $vcsRepoInternalId,
|
||||||
'search' => implode(' ', [$functionId, $name, $function->getAttribute('runtime')]),
|
'search' => implode(' ', [$functionId, $name, $function->getAttribute('runtime')]),
|
||||||
])));
|
])));
|
||||||
|
|
||||||
|
@ -563,6 +656,18 @@ App::put('/v1/functions/:functionId')
|
||||||
|
|
||||||
$eventsInstance->setParam('functionId', $function->getId());
|
$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);
|
$response->dynamic($function, Response::MODEL_FUNCTION);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
481
app/controllers/api/vcs.php
Normal file
481
app/controllers/api/vcs.php
Normal 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();
|
||||||
|
});
|
|
@ -152,6 +152,7 @@ const DELETE_TYPE_ABUSE = 'abuse';
|
||||||
const DELETE_TYPE_USAGE = 'usage';
|
const DELETE_TYPE_USAGE = 'usage';
|
||||||
const DELETE_TYPE_REALTIME = 'realtime';
|
const DELETE_TYPE_REALTIME = 'realtime';
|
||||||
const DELETE_TYPE_BUCKETS = 'buckets';
|
const DELETE_TYPE_BUCKETS = 'buckets';
|
||||||
|
const DELETE_TYPE_INSTALLATIONS = 'vcs_installations';
|
||||||
const DELETE_TYPE_RULES = 'rules';
|
const DELETE_TYPE_RULES = 'rules';
|
||||||
const DELETE_TYPE_SESSIONS = 'sessions';
|
const DELETE_TYPE_SESSIONS = 'sessions';
|
||||||
const DELETE_TYPE_CACHE_BY_TIMESTAMP = 'cacheByTimeStamp';
|
const DELETE_TYPE_CACHE_BY_TIMESTAMP = 'cacheByTimeStamp';
|
||||||
|
|
|
@ -16,6 +16,8 @@ use Utopia\Database\Document;
|
||||||
use Utopia\Config\Config;
|
use Utopia\Config\Config;
|
||||||
use Utopia\Database\Validator\Authorization;
|
use Utopia\Database\Validator\Authorization;
|
||||||
use Utopia\Storage\Storage;
|
use Utopia\Storage\Storage;
|
||||||
|
use Utopia\Database\Validator\Authorization;
|
||||||
|
use Utopia\VCS\Adapter\Git\GitHub;
|
||||||
|
|
||||||
require_once __DIR__ . '/../init.php';
|
require_once __DIR__ . '/../init.php';
|
||||||
|
|
||||||
|
@ -43,12 +45,15 @@ class BuildsV1 extends Worker
|
||||||
$project = new Document($this->args['project'] ?? []);
|
$project = new Document($this->args['project'] ?? []);
|
||||||
$resource = new Document($this->args['resource'] ?? []);
|
$resource = new Document($this->args['resource'] ?? []);
|
||||||
$deployment = new Document($this->args['deployment'] ?? []);
|
$deployment = new Document($this->args['deployment'] ?? []);
|
||||||
|
$SHA = $this->args['SHA'] ?? '';
|
||||||
|
$owner = $this->args['owner'] ?? '';
|
||||||
|
$targetUrl = $this->args['targetUrl'] ?? '';
|
||||||
|
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case BUILD_TYPE_DEPLOYMENT:
|
case BUILD_TYPE_DEPLOYMENT:
|
||||||
case BUILD_TYPE_RETRY:
|
case BUILD_TYPE_RETRY:
|
||||||
Console::info('Creating build for deployment: ' . $deployment->getId());
|
Console::info('Creating build for deployment: ' . $deployment->getId());
|
||||||
$this->buildDeployment($project, $resource, $deployment);
|
$this->buildDeployment($project, $resource, $deployment, $SHA, $owner, $targetUrl);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
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;
|
global $register;
|
||||||
|
|
||||||
$dbForProject = $this->getProjectDB($project);
|
$dbForProject = $this->getProjectDB($project);
|
||||||
|
$dbForConsole = $this->getConsoleDB();
|
||||||
|
|
||||||
$function = $dbForProject->getDocument('functions', $function->getId());
|
$function = $dbForProject->getDocument('functions', $function->getId());
|
||||||
if ($function->isEmpty()) {
|
if ($function->isEmpty()) {
|
||||||
|
@ -80,7 +86,8 @@ class BuildsV1 extends Worker
|
||||||
throw new Exception('Runtime "' . $function->getAttribute('runtime', '') . '" is not supported');
|
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;
|
$device = Storage::DEVICE_LOCAL;
|
||||||
try {
|
try {
|
||||||
$dsn = new DSN($connection);
|
$dsn = new DSN($connection);
|
||||||
|
@ -94,6 +101,94 @@ class BuildsV1 extends Worker
|
||||||
$durationStart = \microtime(true);
|
$durationStart = \microtime(true);
|
||||||
if (empty($buildId)) {
|
if (empty($buildId)) {
|
||||||
$buildId = ID::unique();
|
$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([
|
$build = $dbForProject->createDocument('builds', new Document([
|
||||||
'$id' => $buildId,
|
'$id' => $buildId,
|
||||||
'$permissions' => [],
|
'$permissions' => [],
|
||||||
|
@ -122,6 +217,14 @@ class BuildsV1 extends Worker
|
||||||
$build->setAttribute('status', 'building');
|
$build->setAttribute('status', 'building');
|
||||||
$build = $dbForProject->updateDocument('builds', $buildId, $build);
|
$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 */
|
/** Trigger Webhook */
|
||||||
$deploymentModel = new Deployment();
|
$deploymentModel = new Deployment();
|
||||||
|
|
||||||
|
@ -167,6 +270,10 @@ class BuildsV1 extends Worker
|
||||||
|
|
||||||
$source = $deployment->getAttribute('path');
|
$source = $deployment->getAttribute('path');
|
||||||
|
|
||||||
|
if ($isVcsEnabled) {
|
||||||
|
$source = $path;
|
||||||
|
}
|
||||||
|
|
||||||
$vars = array_reduce($function->getAttribute('vars', []), function (array $carry, Document $var) {
|
$vars = array_reduce($function->getAttribute('vars', []), function (array $carry, Document $var) {
|
||||||
$carry[$var->getAttribute('key')] = $var->getAttribute('value');
|
$carry[$var->getAttribute('key')] = $var->getAttribute('value');
|
||||||
return $carry;
|
return $carry;
|
||||||
|
@ -214,6 +321,22 @@ class BuildsV1 extends Worker
|
||||||
$build->setAttribute('stderr', $response['stderr']);
|
$build->setAttribute('stderr', $response['stderr']);
|
||||||
$build->setAttribute('stdout', $response['stdout']);
|
$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 */
|
/* Also update the deployment buildTime */
|
||||||
$deployment->setAttribute('buildTime', $response['duration']);
|
$deployment->setAttribute('buildTime', $response['duration']);
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,10 @@ class DeletesV1 extends Worker
|
||||||
break;
|
break;
|
||||||
case DELETE_TYPE_BUCKETS:
|
case DELETE_TYPE_BUCKETS:
|
||||||
$this->deleteBucket($document, $project);
|
$this->deleteBucket($document, $project);
|
||||||
|
break;
|
||||||
|
case DELETE_TYPE_INSTALLATIONS:
|
||||||
|
$this->deleteInstallation($document, $project);
|
||||||
|
break;
|
||||||
case DELETE_TYPE_RULES:
|
case DELETE_TYPE_RULES:
|
||||||
$this->deleteRule($document, $project);
|
$this->deleteRule($document, $project);
|
||||||
break;
|
break;
|
||||||
|
@ -329,7 +333,7 @@ class DeletesV1 extends Worker
|
||||||
'teams',
|
'teams',
|
||||||
$teamId,
|
$teamId,
|
||||||
// Ensure that total >= 0
|
// 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");
|
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 string $collection collectionID
|
||||||
* @param Query[] $queries
|
* @param Query[] $queries
|
||||||
|
@ -737,6 +778,25 @@ class DeletesV1 extends Worker
|
||||||
$device->deletePath($document->getId());
|
$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)
|
protected function deleteRuntimes(?Document $function, Document $project)
|
||||||
{
|
{
|
||||||
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
||||||
|
|
|
@ -65,6 +65,7 @@
|
||||||
"utopia-php/registry": "0.5.*",
|
"utopia-php/registry": "0.5.*",
|
||||||
"utopia-php/storage": "0.13.*",
|
"utopia-php/storage": "0.13.*",
|
||||||
"utopia-php/swoole": "0.5.*",
|
"utopia-php/swoole": "0.5.*",
|
||||||
|
"utopia-php/vcs": "dev-feat-git-adapter as 0.1.99",
|
||||||
"utopia-php/websocket": "0.1.0",
|
"utopia-php/websocket": "0.1.0",
|
||||||
"resque/php-resque": "1.3.6",
|
"resque/php-resque": "1.3.6",
|
||||||
"matomo/device-detector": "6.0.0",
|
"matomo/device-detector": "6.0.0",
|
||||||
|
@ -79,6 +80,10 @@
|
||||||
{
|
{
|
||||||
"url": "https://github.com/appwrite/runtimes.git",
|
"url": "https://github.com/appwrite/runtimes.git",
|
||||||
"type": "git"
|
"type": "git"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/utopia-php/vcs.git",
|
||||||
|
"type": "git"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
|
|
@ -177,6 +177,9 @@ services:
|
||||||
- _APP_REGION
|
- _APP_REGION
|
||||||
- _APP_CONSOLE_GITHUB_APP_ID
|
- _APP_CONSOLE_GITHUB_APP_ID
|
||||||
- _APP_CONSOLE_GITHUB_SECRET
|
- _APP_CONSOLE_GITHUB_SECRET
|
||||||
|
- VCS_GITHUB_APP_NAME
|
||||||
|
- VCS_GITHUB_PRIVATE_KEY
|
||||||
|
- VCS_GITHUB_APP_ID
|
||||||
|
|
||||||
appwrite-realtime:
|
appwrite-realtime:
|
||||||
entrypoint: realtime
|
entrypoint: realtime
|
||||||
|
@ -382,7 +385,9 @@ services:
|
||||||
image: appwrite-dev
|
image: appwrite-dev
|
||||||
networks:
|
networks:
|
||||||
- appwrite
|
- appwrite
|
||||||
volumes:
|
volumes:
|
||||||
|
- appwrite-functions:/storage/functions:rw
|
||||||
|
- appwrite-builds:/storage/builds:rw
|
||||||
- ./app:/usr/src/code/app
|
- ./app:/usr/src/code/app
|
||||||
- ./src:/usr/src/code/src
|
- ./src:/usr/src/code/src
|
||||||
depends_on:
|
depends_on:
|
||||||
|
@ -412,6 +417,9 @@ services:
|
||||||
- _APP_CONNECTIONS_STORAGE
|
- _APP_CONNECTIONS_STORAGE
|
||||||
- _APP_LOGGING_PROVIDER
|
- _APP_LOGGING_PROVIDER
|
||||||
- _APP_LOGGING_CONFIG
|
- _APP_LOGGING_CONFIG
|
||||||
|
- VCS_GITHUB_APP_NAME
|
||||||
|
- VCS_GITHUB_PRIVATE_KEY
|
||||||
|
- VCS_GITHUB_APP_ID
|
||||||
|
|
||||||
appwrite-worker-certificates:
|
appwrite-worker-certificates:
|
||||||
entrypoint: worker-certificates
|
entrypoint: worker-certificates
|
||||||
|
|
|
@ -10,12 +10,54 @@ class Build extends Event
|
||||||
protected string $type = '';
|
protected string $type = '';
|
||||||
protected ?Document $resource = null;
|
protected ?Document $resource = null;
|
||||||
protected ?Document $deployment = null;
|
protected ?Document $deployment = null;
|
||||||
|
protected string $SHA = '';
|
||||||
|
protected string $owner = '';
|
||||||
|
protected string $targetUrl = '';
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct(Event::BUILDS_QUEUE_NAME, Event::BUILDS_CLASS_NAME);
|
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.
|
* Sets resource document for the build event.
|
||||||
*
|
*
|
||||||
|
@ -97,7 +139,10 @@ class Build extends Event
|
||||||
'project' => $this->project,
|
'project' => $this->project,
|
||||||
'resource' => $this->resource,
|
'resource' => $this->resource,
|
||||||
'deployment' => $this->deployment,
|
'deployment' => $this->deployment,
|
||||||
'type' => $this->type
|
'type' => $this->type,
|
||||||
|
'SHA' => $this->SHA,
|
||||||
|
'owner' => $this->owner,
|
||||||
|
'targetUrl' => $this->targetUrl
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,9 @@ class Exception extends \Exception
|
||||||
public const STORAGE_INVALID_CONTENT_RANGE = 'storage_invalid_content_range';
|
public const STORAGE_INVALID_CONTENT_RANGE = 'storage_invalid_content_range';
|
||||||
public const STORAGE_INVALID_RANGE = 'storage_invalid_range';
|
public const STORAGE_INVALID_RANGE = 'storage_invalid_range';
|
||||||
|
|
||||||
|
/** VCS */
|
||||||
|
public const INSTALLATION_NOT_FOUND = 'installation_not_found';
|
||||||
|
|
||||||
/** Functions */
|
/** Functions */
|
||||||
public const FUNCTION_NOT_FOUND = 'function_not_found';
|
public const FUNCTION_NOT_FOUND = 'function_not_found';
|
||||||
public const FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported';
|
public const FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported';
|
||||||
|
|
|
@ -334,6 +334,7 @@ class OpenAPI3 extends Format
|
||||||
case 'Appwrite\Utopia\Database\Validator\Queries\Collections':
|
case 'Appwrite\Utopia\Database\Validator\Queries\Collections':
|
||||||
case 'Appwrite\Utopia\Database\Validator\Queries\Databases':
|
case 'Appwrite\Utopia\Database\Validator\Queries\Databases':
|
||||||
case 'Appwrite\Utopia\Database\Validator\Queries\Deployments':
|
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\Documents':
|
||||||
case 'Appwrite\Utopia\Database\Validator\Queries\Executions':
|
case 'Appwrite\Utopia\Database\Validator\Queries\Executions':
|
||||||
case 'Appwrite\Utopia\Database\Validator\Queries\Files':
|
case 'Appwrite\Utopia\Database\Validator\Queries\Files':
|
||||||
|
|
|
@ -328,6 +328,7 @@ class Swagger2 extends Format
|
||||||
case 'Appwrite\Utopia\Database\Validator\Queries\Collections':
|
case 'Appwrite\Utopia\Database\Validator\Queries\Collections':
|
||||||
case 'Appwrite\Utopia\Database\Validator\Queries\Databases':
|
case 'Appwrite\Utopia\Database\Validator\Queries\Databases':
|
||||||
case 'Appwrite\Utopia\Database\Validator\Queries\Deployments':
|
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\Documents':
|
||||||
case 'Appwrite\Utopia\Database\Validator\Queries\Executions':
|
case 'Appwrite\Utopia\Database\Validator\Queries\Executions':
|
||||||
case 'Appwrite\Utopia\Database\Validator\Queries\Files':
|
case 'Appwrite\Utopia\Database\Validator\Queries\Files':
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -70,6 +70,8 @@ use Appwrite\Utopia\Response\Model\HealthQueue;
|
||||||
use Appwrite\Utopia\Response\Model\HealthStatus;
|
use Appwrite\Utopia\Response\Model\HealthStatus;
|
||||||
use Appwrite\Utopia\Response\Model\HealthTime;
|
use Appwrite\Utopia\Response\Model\HealthTime;
|
||||||
use Appwrite\Utopia\Response\Model\HealthVersion;
|
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\Mock; // Keep last
|
||||||
use Appwrite\Utopia\Response\Model\Provider;
|
use Appwrite\Utopia\Response\Model\Provider;
|
||||||
use Appwrite\Utopia\Response\Model\Runtime;
|
use Appwrite\Utopia\Response\Model\Runtime;
|
||||||
|
@ -176,6 +178,12 @@ class Response extends SwooleResponse
|
||||||
public const MODEL_MEMBERSHIP = 'membership';
|
public const MODEL_MEMBERSHIP = 'membership';
|
||||||
public const MODEL_MEMBERSHIP_LIST = 'membershipList';
|
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
|
// Functions
|
||||||
public const MODEL_FUNCTION = 'function';
|
public const MODEL_FUNCTION = 'function';
|
||||||
public const MODEL_FUNCTION_LIST = 'functionList';
|
public const MODEL_FUNCTION_LIST = 'functionList';
|
||||||
|
@ -207,6 +215,7 @@ class Response extends SwooleResponse
|
||||||
public const MODEL_PLATFORM_LIST = 'platformList';
|
public const MODEL_PLATFORM_LIST = 'platformList';
|
||||||
public const MODEL_VARIABLE = 'variable';
|
public const MODEL_VARIABLE = 'variable';
|
||||||
public const MODEL_VARIABLE_LIST = 'variableList';
|
public const MODEL_VARIABLE_LIST = 'variableList';
|
||||||
|
public const MODEL_VCS = 'vcs';
|
||||||
|
|
||||||
// Health
|
// Health
|
||||||
public const MODEL_HEALTH_STATUS = 'healthStatus';
|
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('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('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('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('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('Deployments List', self::MODEL_DEPLOYMENT_LIST, 'deployments', self::MODEL_DEPLOYMENT))
|
||||||
->setModel(new BaseList('Executions List', self::MODEL_EXECUTION_LIST, 'executions', self::MODEL_EXECUTION))
|
->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 Team())
|
||||||
->setModel(new Membership())
|
->setModel(new Membership())
|
||||||
->setModel(new Func())
|
->setModel(new Func())
|
||||||
|
->setModel(new Installation())
|
||||||
|
->setModel(new Repository())
|
||||||
->setModel(new Runtime())
|
->setModel(new Runtime())
|
||||||
->setModel(new Deployment())
|
->setModel(new Deployment())
|
||||||
->setModel(new Execution())
|
->setModel(new Execution())
|
||||||
|
|
|
@ -111,6 +111,18 @@ class Func extends Model
|
||||||
'default' => '',
|
'default' => '',
|
||||||
'example' => 'npm install',
|
'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',
|
||||||
|
])
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
72
src/Appwrite/Utopia/Response/Model/Installation.php
Normal file
72
src/Appwrite/Utopia/Response/Model/Installation.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
52
src/Appwrite/Utopia/Response/Model/Repository.php
Normal file
52
src/Appwrite/Utopia/Response/Model/Repository.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue