mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch 'feat-sites' into chore-rules-rehaul
This commit is contained in:
+106
-203
@@ -1154,7 +1154,6 @@ return [
|
||||
]
|
||||
],
|
||||
],
|
||||
|
||||
'deployments' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('deployments'),
|
||||
@@ -1193,28 +1192,6 @@ return [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('buildInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('buildId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('entrypoint'),
|
||||
@@ -1228,18 +1205,7 @@ return [
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('commands'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 2048,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('buildCommand'),
|
||||
'$id' => ID::custom('buildCommands'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
@@ -1250,7 +1216,7 @@ return [
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('installCommand'),
|
||||
'$id' => ID::custom('buildOutput'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
@@ -1260,18 +1226,7 @@ return [
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'array' => false,
|
||||
'$id' => ID::custom('outputDirectory'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('path'),
|
||||
'$id' => ID::custom('sourcePath'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 2048,
|
||||
@@ -1285,7 +1240,7 @@ return [
|
||||
'$id' => ID::custom('type'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 2048,
|
||||
'size' => 32,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
@@ -1464,7 +1419,7 @@ return [
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('size'),
|
||||
'$id' => ID::custom('sourceSize'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
@@ -1475,7 +1430,7 @@ return [
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('metadata'),
|
||||
'$id' => ID::custom('sourceMetadata'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16384, // https://tools.ietf.org/html/rfc4288#section-4.2
|
||||
@@ -1486,7 +1441,7 @@ return [
|
||||
'filters' => ['json'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('chunksTotal'),
|
||||
'$id' => ID::custom('sourceChunksTotal'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
@@ -1497,7 +1452,7 @@ return [
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('chunksUploaded'),
|
||||
'$id' => ID::custom('sourceChunksUploaded'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
@@ -1551,6 +1506,83 @@ return [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('buildStartAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('buildEndAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('buildDuration'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('buildSize'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('status'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 16,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => 'processing',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('buildPath'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('buildLogs'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 1000000,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
]
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
@@ -1575,16 +1607,23 @@ return [
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_size'),
|
||||
'$id' => ID::custom('_key_sourceSize'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['size'],
|
||||
'attributes' => ['sourceSize'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_buildId'),
|
||||
'$id' => ID::custom('_key_buildSize'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['buildId'],
|
||||
'attributes' => ['buildSize'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_buildDuration'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['buildDuration'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
@@ -1595,155 +1634,19 @@ return [
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'builds' => [
|
||||
'$collection' => ID::custom(Database::METADATA),
|
||||
'$id' => ID::custom('builds'),
|
||||
'name' => 'Builds',
|
||||
'attributes' => [
|
||||
[
|
||||
'$id' => ID::custom('startTime'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('endTime'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('duration'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('size'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('deploymentInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('deploymentId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('runtime'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 2048,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('status'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => 'processing',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('path'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 2048,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('logs'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 1000000,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('sourceType'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 2048,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => 'local',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('source'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 2048,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => '',
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
]
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_deployment'),
|
||||
'$id' => ID::custom('_key_type'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['deploymentId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'attributes' => ['type'],
|
||||
'lengths' => [32],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
]
|
||||
], [
|
||||
'$id' => ID::custom('_key_status'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['status'],
|
||||
'lengths' => [16],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
|
||||
@@ -527,7 +527,7 @@ return [
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::FUNCTION_ENTRYPOINT_MISSING => [
|
||||
'name' => Exception::FUNCTION_RUNTIME_UNSUPPORTED,
|
||||
'name' => Exception::FUNCTION_ENTRYPOINT_MISSING,
|
||||
'description' => 'Entrypoint for your Appwrite Function is missing. Please specify it when making deployment or update the entrypoint under your function\'s "Settings" > "Configuration" > "Entrypoint".',
|
||||
'code' => 404,
|
||||
],
|
||||
@@ -541,6 +541,11 @@ return [
|
||||
'description' => 'Function Template with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::FUNCTION_RUNTIME_NOT_DETECTED => [
|
||||
'name' => Exception::FUNCTION_RUNTIME_NOT_DETECTED,
|
||||
'description' => 'Function runtime could not be detected.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** Sites */
|
||||
Exception::SITE_NOT_FOUND => [
|
||||
|
||||
+10
-28
@@ -23,6 +23,8 @@ return [
|
||||
'name' => 'Next.js',
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'bundleCommand' => 'sh /usr/local/server/helpers/next-js/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/next-js/env.sh',
|
||||
'adapters' => [
|
||||
'ssr' => [
|
||||
'key' => 'ssr',
|
||||
@@ -30,8 +32,6 @@ return [
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './.next',
|
||||
'startCommand' => 'sh helpers/next-js/server.sh',
|
||||
'bundleCommand' => 'sh /usr/local/server/helpers/next-js/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/next-js/env.sh',
|
||||
],
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
@@ -39,8 +39,6 @@ return [
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './out',
|
||||
'startCommand' => 'sh helpers/server.sh',
|
||||
'bundleCommand' => '',
|
||||
'envCommand' => '',
|
||||
]
|
||||
]
|
||||
],
|
||||
@@ -56,8 +54,6 @@ return [
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './dist',
|
||||
'startCommand' => 'sh helpers/server.sh',
|
||||
'bundleCommand' => '',
|
||||
'envCommand' => '',
|
||||
'fallbackFile' => 'index.html'
|
||||
]
|
||||
]
|
||||
@@ -67,6 +63,8 @@ return [
|
||||
'name' => 'Nuxt',
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'bundleCommand' => 'sh /usr/local/server/helpers/nuxt/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/nuxt/env.sh',
|
||||
'adapters' => [
|
||||
'ssr' => [
|
||||
'key' => 'ssr',
|
||||
@@ -74,8 +72,6 @@ return [
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './.output',
|
||||
'startCommand' => 'sh helpers/nuxt/server.sh',
|
||||
'bundleCommand' => 'sh /usr/local/server/helpers/nuxt/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/nuxt/env.sh',
|
||||
],
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
@@ -83,8 +79,6 @@ return [
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './dist',
|
||||
'startCommand' => 'sh helpers/server.sh',
|
||||
'bundleCommand' => '',
|
||||
'envCommand' => '',
|
||||
]
|
||||
]
|
||||
],
|
||||
@@ -100,8 +94,6 @@ return [
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './dist',
|
||||
'startCommand' => 'sh helpers/server.sh',
|
||||
'bundleCommand' => '',
|
||||
'envCommand' => '',
|
||||
'fallbackFile' => 'index.html'
|
||||
]
|
||||
]
|
||||
@@ -111,6 +103,8 @@ return [
|
||||
'name' => 'SvelteKit',
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'bundleCommand' => 'sh /usr/local/server/helpers/sveltekit/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/sveltekit/env.sh',
|
||||
'adapters' => [
|
||||
'ssr' => [
|
||||
'key' => 'ssr',
|
||||
@@ -118,8 +112,6 @@ return [
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './build',
|
||||
'startCommand' => 'sh helpers/sveltekit/server.sh',
|
||||
'bundleCommand' => 'sh /usr/local/server/helpers/sveltekit/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/sveltekit/env.sh',
|
||||
],
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
@@ -127,8 +119,6 @@ return [
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './build',
|
||||
'startCommand' => 'sh helpers/server.sh',
|
||||
'bundleCommand' => '',
|
||||
'envCommand' => '',
|
||||
]
|
||||
]
|
||||
],
|
||||
@@ -137,6 +127,8 @@ return [
|
||||
'name' => 'Astro',
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'bundleCommand' => 'sh /usr/local/server/helpers/astro/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/astro/env.sh',
|
||||
'adapters' => [
|
||||
'ssr' => [
|
||||
'key' => 'ssr',
|
||||
@@ -144,8 +136,6 @@ return [
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './dist',
|
||||
'startCommand' => 'sh helpers/astro/server.sh',
|
||||
'bundleCommand' => 'sh /usr/local/server/helpers/astro/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/astro/env.sh',
|
||||
],
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
@@ -153,8 +143,6 @@ return [
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './dist',
|
||||
'startCommand' => 'sh helpers/server.sh',
|
||||
'bundleCommand' => '',
|
||||
'envCommand' => '',
|
||||
]
|
||||
]
|
||||
],
|
||||
@@ -163,6 +151,8 @@ return [
|
||||
'name' => 'Remix',
|
||||
'buildRuntime' => 'node-22',
|
||||
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
|
||||
'bundleCommand' => 'sh /usr/local/server/helpers/remix/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/remix/env.sh',
|
||||
'adapters' => [
|
||||
'ssr' => [
|
||||
'key' => 'ssr',
|
||||
@@ -170,8 +160,6 @@ return [
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './build',
|
||||
'startCommand' => 'sh helpers/remix/server.sh',
|
||||
'bundleCommand' => 'sh /usr/local/server/helpers/remix/bundle.sh',
|
||||
'envCommand' => 'source /usr/local/server/helpers/remix/env.sh',
|
||||
],
|
||||
'static' => [
|
||||
'key' => 'static',
|
||||
@@ -179,8 +167,6 @@ return [
|
||||
'installCommand' => 'npm install',
|
||||
'outputDirectory' => './build/client',
|
||||
'startCommand' => 'sh helpers/server.sh',
|
||||
'bundleCommand' => '',
|
||||
'envCommand' => '',
|
||||
]
|
||||
]
|
||||
],
|
||||
@@ -196,8 +182,6 @@ return [
|
||||
'installCommand' => '',
|
||||
'outputDirectory' => './build/web',
|
||||
'startCommand' => 'sh helpers/server.sh',
|
||||
'bundleCommand' => '',
|
||||
'envCommand' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
@@ -213,8 +197,6 @@ return [
|
||||
'installCommand' => '',
|
||||
'outputDirectory' => './',
|
||||
'startCommand' => 'sh helpers/server.sh',
|
||||
'bundleCommand' => '',
|
||||
'envCommand' => '',
|
||||
],
|
||||
]
|
||||
],
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Functions\Specification;
|
||||
|
||||
return [
|
||||
Specification::S_05VCPU_512MB => [
|
||||
'slug' => Specification::S_05VCPU_512MB,
|
||||
'memory' => 512,
|
||||
'cpus' => 0.5
|
||||
],
|
||||
Specification::S_1VCPU_512MB => [
|
||||
'slug' => Specification::S_1VCPU_512MB,
|
||||
'memory' => 512,
|
||||
'cpus' => 1
|
||||
],
|
||||
Specification::S_1VCPU_1GB => [
|
||||
'slug' => Specification::S_1VCPU_1GB,
|
||||
'memory' => 1024,
|
||||
'cpus' => 1
|
||||
],
|
||||
Specification::S_2VCPU_2GB => [
|
||||
'slug' => Specification::S_2VCPU_2GB,
|
||||
'memory' => 2048,
|
||||
'cpus' => 2
|
||||
],
|
||||
Specification::S_2VCPU_4GB => [
|
||||
'slug' => Specification::S_2VCPU_4GB,
|
||||
'memory' => 4096,
|
||||
'cpus' => 2
|
||||
],
|
||||
Specification::S_4VCPU_4GB => [
|
||||
'slug' => Specification::S_4VCPU_4GB,
|
||||
'memory' => 4096,
|
||||
'cpus' => 4
|
||||
],
|
||||
Specification::S_4VCPU_8GB => [
|
||||
'slug' => Specification::S_4VCPU_8GB,
|
||||
'memory' => 8192,
|
||||
'cpus' => 4
|
||||
],
|
||||
Specification::S_8VCPU_4GB => [
|
||||
'slug' => Specification::S_8VCPU_4GB,
|
||||
'memory' => 4096,
|
||||
'cpus' => 8
|
||||
],
|
||||
Specification::S_8VCPU_8GB => [
|
||||
'slug' => Specification::S_8VCPU_8GB,
|
||||
'memory' => 8192,
|
||||
'cpus' => 8
|
||||
]
|
||||
];
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Sites\Specification;
|
||||
use Appwrite\Platform\Modules\Compute\Specification;
|
||||
|
||||
return [
|
||||
Specification::S_05VCPU_512MB => [
|
||||
@@ -27,7 +27,7 @@ const TEMPLATE_FRAMEWORKS = [
|
||||
'outputDirectory' => './build',
|
||||
'buildRuntime' => 'node-22',
|
||||
'adapter' => 'ssr',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
],
|
||||
'NEXTJS' => [
|
||||
'key' => 'nextjs',
|
||||
@@ -37,7 +37,7 @@ const TEMPLATE_FRAMEWORKS = [
|
||||
'outputDirectory' => './.next',
|
||||
'buildRuntime' => 'node-22',
|
||||
'adapter' => 'ssr',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
],
|
||||
'NUXT' => [
|
||||
'key' => 'nuxt',
|
||||
@@ -47,7 +47,7 @@ const TEMPLATE_FRAMEWORKS = [
|
||||
'outputDirectory' => './.output',
|
||||
'buildRuntime' => 'node-22',
|
||||
'adapter' => 'ssr',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
],
|
||||
'REMIX' => [
|
||||
'key' => 'remix',
|
||||
@@ -57,7 +57,7 @@ const TEMPLATE_FRAMEWORKS = [
|
||||
'outputDirectory' => './build',
|
||||
'buildRuntime' => 'node-22',
|
||||
'adapter' => 'ssr',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
],
|
||||
'ASTRO' => [
|
||||
'key' => 'astro',
|
||||
@@ -67,7 +67,7 @@ const TEMPLATE_FRAMEWORKS = [
|
||||
'outputDirectory' => './dist',
|
||||
'buildRuntime' => 'node-22',
|
||||
'adapter' => 'ssr',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
],
|
||||
'FLUTTER' => [
|
||||
'key' => 'flutter',
|
||||
@@ -77,7 +77,7 @@ const TEMPLATE_FRAMEWORKS = [
|
||||
'outputDirectory' => './build/web',
|
||||
'buildRuntime' => 'flutter-3.24',
|
||||
'adapter' => 'static',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
],
|
||||
'OTHER' => [
|
||||
'key' => 'other',
|
||||
+228
-75
@@ -14,7 +14,6 @@ use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Vcs\Comment;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
@@ -27,22 +26,35 @@ use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Detector\Adapter\Bun;
|
||||
use Utopia\Detector\Adapter\CPP;
|
||||
use Utopia\Detector\Adapter\Dart;
|
||||
use Utopia\Detector\Adapter\Deno;
|
||||
use Utopia\Detector\Adapter\Dotnet;
|
||||
use Utopia\Detector\Adapter\Java;
|
||||
use Utopia\Detector\Adapter\JavaScript;
|
||||
use Utopia\Detector\Adapter\PHP;
|
||||
use Utopia\Detector\Adapter\Python;
|
||||
use Utopia\Detector\Adapter\Ruby;
|
||||
use Utopia\Detector\Adapter\Swift;
|
||||
use Utopia\Detector\Detector;
|
||||
use Utopia\Detector\Detection\Framework\Astro;
|
||||
use Utopia\Detector\Detection\Framework\Flutter;
|
||||
use Utopia\Detector\Detection\Framework\NextJs;
|
||||
use Utopia\Detector\Detection\Framework\Nuxt;
|
||||
use Utopia\Detector\Detection\Framework\Remix;
|
||||
use Utopia\Detector\Detection\Framework\SvelteKit;
|
||||
use Utopia\Detector\Detection\Packager\NPM;
|
||||
use Utopia\Detector\Detection\Packager\PNPM;
|
||||
use Utopia\Detector\Detection\Packager\Yarn;
|
||||
use Utopia\Detector\Detection\Runtime\Bun;
|
||||
use Utopia\Detector\Detection\Runtime\CPP;
|
||||
use Utopia\Detector\Detection\Runtime\Dart;
|
||||
use Utopia\Detector\Detection\Runtime\Deno;
|
||||
use Utopia\Detector\Detection\Runtime\Dotnet;
|
||||
use Utopia\Detector\Detection\Runtime\Java;
|
||||
use Utopia\Detector\Detection\Runtime\Node;
|
||||
use Utopia\Detector\Detection\Runtime\PHP;
|
||||
use Utopia\Detector\Detection\Runtime\Python;
|
||||
use Utopia\Detector\Detection\Runtime\Ruby;
|
||||
use Utopia\Detector\Detection\Runtime\Swift;
|
||||
use Utopia\Detector\Detector\Framework;
|
||||
use Utopia\Detector\Detector\Packager;
|
||||
use Utopia\Detector\Detector\Runtime;
|
||||
use Utopia\Detector\Detector\Strategy;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Host;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
use Utopia\VCS\Exception\RepositoryNotFound;
|
||||
|
||||
@@ -196,6 +208,17 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
$providerRepositoryOwner = $pullRequestResponse['head']['repo']['name'];
|
||||
}
|
||||
|
||||
$commands = [];
|
||||
if (!empty($resource->getAttribute('buildCommand', ''))) {
|
||||
$commands[] = $resource->getAttribute('buildCommand', '');
|
||||
}
|
||||
if (!empty($resource->getAttribute('installCommand', ''))) {
|
||||
$commands[] = $resource->getAttribute('installCommand', '');
|
||||
}
|
||||
if (!empty($resource->getAttribute('commands', ''))) {
|
||||
$commands[] = $resource->getAttribute('commands', '');
|
||||
}
|
||||
|
||||
$deployment = $dbForProject->createDocument('deployments', new Document([
|
||||
'$id' => $deploymentId,
|
||||
'$permissions' => [
|
||||
@@ -207,10 +230,8 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
'resourceInternalId' => $resourceInternalId,
|
||||
'resourceType' => $resourceCollection,
|
||||
'entrypoint' => $resource->getAttribute('entrypoint', ''),
|
||||
'commands' => $resource->getAttribute('commands', ''),
|
||||
'installCommand' => $resource->getAttribute('installCommand', ''),
|
||||
'buildCommand' => $resource->getAttribute('buildCommand', ''),
|
||||
'outputDirectory' => $resource->getAttribute('outputDirectory', ''),
|
||||
'buildCommands' => \implode(' && ', $commands),
|
||||
'buildOutput' => $resource->getAttribute('outputDirectory', ''),
|
||||
'type' => 'vcs',
|
||||
'installationId' => $installationId,
|
||||
'installationInternalId' => $installationInternalId,
|
||||
@@ -608,8 +629,9 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories/:pro
|
||||
]), Response::MODEL_VCS_CONTENT_LIST);
|
||||
});
|
||||
|
||||
App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/detection')
|
||||
->desc('Detect runtime settings from source code')
|
||||
App::post('/v1/vcs/github/installations/:installationId/detections')
|
||||
->alias('/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/detection')
|
||||
->desc('Create repository detection')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.write')
|
||||
->label('sdk', new Method(
|
||||
@@ -620,18 +642,22 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:pr
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_DETECTION,
|
||||
model: Response::MODEL_DETECTION_RUNTIME,
|
||||
),
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_DETECTION_FRAMEWORK,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('installationId', '', new Text(256), 'Installation Id')
|
||||
->param('providerRepositoryId', '', new Text(256), 'Repository Id')
|
||||
->param('type', '', new WhiteList(['runtime', 'framework']), 'Detector type. Must be one of the following: runtime, framework')
|
||||
->param('providerRootDirectory', '', new Text(256, 0), 'Path to Root Directory', true)
|
||||
->inject('gitHub')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $installationId, string $providerRepositoryId, string $providerRootDirectory, GitHub $github, Response $response, Document $project, Database $dbForPlatform) {
|
||||
->action(function (string $installationId, string $providerRepositoryId, string $type, string $providerRootDirectory, GitHub $github, Response $response, Database $dbForPlatform) {
|
||||
$installation = $dbForPlatform->getDocument('installations', $installationId);
|
||||
|
||||
if ($installation->isEmpty()) {
|
||||
@@ -657,32 +683,108 @@ App::post('/v1/vcs/github/installations/:installationId/providerRepositories/:pr
|
||||
$files = \array_column($files, 'name');
|
||||
$languages = $github->listRepositoryLanguages($owner, $repositoryName);
|
||||
|
||||
$detectorFactory = new Detector($files, $languages);
|
||||
$detector = new Packager($files);
|
||||
$detector
|
||||
->addOption(new Yarn())
|
||||
->addOption(new PNPM())
|
||||
->addOption(new NPM());
|
||||
$detection = $detector->detect();
|
||||
|
||||
$detectorFactory
|
||||
->addDetector(new JavaScript())
|
||||
->addDetector(new Bun())
|
||||
->addDetector(new PHP())
|
||||
->addDetector(new Python())
|
||||
->addDetector(new Dart())
|
||||
->addDetector(new Swift())
|
||||
->addDetector(new Ruby())
|
||||
->addDetector(new Java())
|
||||
->addDetector(new CPP())
|
||||
->addDetector(new Deno())
|
||||
->addDetector(new Dotnet());
|
||||
$packager = !\is_null($detection) ? $detection->getName() : 'npm';
|
||||
|
||||
$runtime = $detectorFactory->detect();
|
||||
if ($type === 'framework') {
|
||||
$output = new Document([
|
||||
'framework' => '',
|
||||
'installCommand' => '',
|
||||
'buildCommand' => '',
|
||||
'outputDirectory' => '',
|
||||
]);
|
||||
|
||||
$runtimes = Config::getParam('runtimes');
|
||||
$runtimeDetail = \array_reverse(\array_filter(\array_keys($runtimes), function ($key) use ($runtime, $runtimes) {
|
||||
return $runtimes[$key]['key'] === $runtime;
|
||||
}))[0] ?? '';
|
||||
$detector = new Framework($files, $packager);
|
||||
$detector
|
||||
->addOption(new Flutter())
|
||||
->addOption(new Nuxt())
|
||||
->addOption(new Astro())
|
||||
->addOption(new Remix())
|
||||
->addOption(new SvelteKit())
|
||||
->addOption(new NextJs());
|
||||
|
||||
$detection = [];
|
||||
$detection['runtime'] = $runtimeDetail;
|
||||
$framework = $detector->detect();
|
||||
|
||||
$response->dynamic(new Document($detection), Response::MODEL_DETECTION);
|
||||
if (!\is_null($framework)) {
|
||||
$framework = $framework->getName();
|
||||
$output->setAttribute('installCommand', $framework->getInstallCommand());
|
||||
$output->setAttribute('buildCommand', $framework->getBuildCommand());
|
||||
$output->setAttribute('outputDirectory', $framework->getOutputDirectory());
|
||||
} else {
|
||||
$framework = 'other';
|
||||
$output->setAttribute('installCommand', '');
|
||||
$output->setAttribute('buildCommand', '');
|
||||
$output->setAttribute('outputDirectory', '');
|
||||
}
|
||||
|
||||
$frameworks = Config::getParam('frameworks');
|
||||
if (!\in_array($framework, array_keys($frameworks), true)) {
|
||||
$framework = 'other';
|
||||
}
|
||||
$output->setAttribute('framework', $framework);
|
||||
} else {
|
||||
$output = new Document([
|
||||
'runtime' => '',
|
||||
'commands' => '',
|
||||
'entrypoint' => '',
|
||||
]);
|
||||
|
||||
$strategies = [
|
||||
new Strategy(Strategy::FILEMATCH),
|
||||
new Strategy(Strategy::LANGUAGES),
|
||||
new Strategy(Strategy::EXTENSION),
|
||||
];
|
||||
|
||||
foreach ($strategies as $strategy) {
|
||||
$detector = new Runtime($strategy === Strategy::LANGUAGES ? $languages : $files, $strategy, $packager);
|
||||
$detector
|
||||
->addOption(new Node())
|
||||
->addOption(new Bun())
|
||||
->addOption(new Deno())
|
||||
->addOption(new PHP())
|
||||
->addOption(new Python())
|
||||
->addOption(new Dart())
|
||||
->addOption(new Swift())
|
||||
->addOption(new Ruby())
|
||||
->addOption(new Java())
|
||||
->addOption(new CPP())
|
||||
->addOption(new Dotnet());
|
||||
|
||||
$runtime = $detector->detect();
|
||||
|
||||
if (!\is_null($runtime)) {
|
||||
$output->setAttribute('commands', $runtime->getCommands());
|
||||
$output->setAttribute('entrypoint', $runtime->getEntrypoint());
|
||||
$runtime = $runtime->getName();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($runtime)) {
|
||||
$runtimes = Config::getParam('runtimes');
|
||||
$runtimeWithVersion = '';
|
||||
foreach ($runtimes as $runtimeKey => $runtimeConfig) {
|
||||
if ($runtimeConfig['key'] === $runtime) {
|
||||
$runtimeWithVersion = $runtimeKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($runtimeWithVersion)) {
|
||||
throw new Exception(Exception::FUNCTION_RUNTIME_NOT_DETECTED);
|
||||
}
|
||||
|
||||
$output->setAttribute('runtime', $runtimeWithVersion);
|
||||
} else {
|
||||
throw new Exception(Exception::FUNCTION_RUNTIME_NOT_DETECTED);
|
||||
}
|
||||
}
|
||||
$response->dynamic($output, $type === 'framework' ? Response::MODEL_DETECTION_FRAMEWORK : Response::MODEL_DETECTION_RUNTIME);
|
||||
});
|
||||
|
||||
App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
@@ -697,17 +799,21 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROVIDER_REPOSITORY_LIST,
|
||||
model: Response::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST,
|
||||
),
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('installationId', '', new Text(256), 'Installation Id')
|
||||
->param('type', '', new WhiteList(['runtime', 'framework']), 'Detector type. Must be one of the following: runtime, framework')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('gitHub')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $installationId, string $search, GitHub $github, Response $response, Document $project, Database $dbForPlatform) {
|
||||
->action(function (string $installationId, string $type, string $search, GitHub $github, Response $response, Database $dbForPlatform) {
|
||||
if (empty($search)) {
|
||||
$search = "";
|
||||
}
|
||||
@@ -737,39 +843,86 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
return $repo;
|
||||
}, $repos);
|
||||
|
||||
$repos = batch(\array_map(function ($repo) use ($github) {
|
||||
return function () use ($repo, $github) {
|
||||
try {
|
||||
$files = $github->listRepositoryContents($repo['organization'], $repo['name'], '');
|
||||
$files = \array_column($files, 'name');
|
||||
$repos = batch(\array_map(function ($repo) use ($type, $github) {
|
||||
return function () use ($repo, $type, $github) {
|
||||
$files = $github->listRepositoryContents($repo['organization'], $repo['name'], '');
|
||||
$files = \array_column($files, 'name');
|
||||
|
||||
$detector = new Packager($files);
|
||||
$detector
|
||||
->addOption(new Yarn())
|
||||
->addOption(new PNPM())
|
||||
->addOption(new NPM());
|
||||
$detection = $detector->detect();
|
||||
|
||||
$packager = !\is_null($detection) ? $detection->getName() : 'npm';
|
||||
|
||||
if ($type === 'framework') {
|
||||
$frameworkDetector = new Framework($files, $packager);
|
||||
$frameworkDetector
|
||||
->addOption(new Flutter())
|
||||
->addOption(new Nuxt())
|
||||
->addOption(new Astro())
|
||||
->addOption(new Remix())
|
||||
->addOption(new SvelteKit())
|
||||
->addOption(new NextJs());
|
||||
|
||||
$detectedFramework = $frameworkDetector->detect();
|
||||
|
||||
if (!\is_null($detectedFramework)) {
|
||||
$framework = $detectedFramework->getName();
|
||||
} else {
|
||||
$framework = 'other';
|
||||
}
|
||||
|
||||
$frameworks = Config::getParam('frameworks');
|
||||
if (!\in_array($framework, array_keys($frameworks), true)) {
|
||||
$framework = 'other';
|
||||
}
|
||||
$repo['framework'] = $framework;
|
||||
} else {
|
||||
$languages = $github->listRepositoryLanguages($repo['organization'], $repo['name']);
|
||||
|
||||
$detectorFactory = new Detector($files, $languages);
|
||||
$strategies = [
|
||||
new Strategy(Strategy::FILEMATCH),
|
||||
new Strategy(Strategy::LANGUAGES),
|
||||
new Strategy(Strategy::EXTENSION),
|
||||
];
|
||||
|
||||
$detectorFactory
|
||||
->addDetector(new JavaScript())
|
||||
->addDetector(new Bun())
|
||||
->addDetector(new PHP())
|
||||
->addDetector(new Python())
|
||||
->addDetector(new Dart())
|
||||
->addDetector(new Swift())
|
||||
->addDetector(new Ruby())
|
||||
->addDetector(new Java())
|
||||
->addDetector(new CPP())
|
||||
->addDetector(new Deno())
|
||||
->addDetector(new Dotnet());
|
||||
foreach ($strategies as $strategy) {
|
||||
$detector = new Runtime($strategy === Strategy::LANGUAGES ? $languages : $files, $strategy, $packager);
|
||||
$detector
|
||||
->addOption(new Node())
|
||||
->addOption(new Bun())
|
||||
->addOption(new Deno())
|
||||
->addOption(new PHP())
|
||||
->addOption(new Python())
|
||||
->addOption(new Dart())
|
||||
->addOption(new Swift())
|
||||
->addOption(new Ruby())
|
||||
->addOption(new Java())
|
||||
->addOption(new CPP())
|
||||
->addOption(new Dotnet());
|
||||
|
||||
$runtime = $detectorFactory->detect();
|
||||
$runtime = $detector->detect();
|
||||
|
||||
$runtimes = Config::getParam('runtimes');
|
||||
$runtimeDetail = \array_reverse(\array_filter(\array_keys($runtimes), function ($key) use ($runtime, $runtimes) {
|
||||
return $runtimes[$key]['key'] === $runtime;
|
||||
}))[0] ?? '';
|
||||
if (!\is_null($runtime)) {
|
||||
$runtime = $runtime->getName();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$repo['runtime'] = $runtimeDetail;
|
||||
} catch (Throwable $error) {
|
||||
$repo['runtime'] = "";
|
||||
Console::warning("Runtime not detected for " . $repo['organization'] . "/" . $repo['name']);
|
||||
if (!empty($runtime)) {
|
||||
$runtimes = Config::getParam('runtimes');
|
||||
$runtimeWithVersion = '';
|
||||
foreach ($runtimes as $runtimeKey => $runtimeConfig) {
|
||||
if ($runtimeConfig['key'] === $runtime) {
|
||||
$runtimeWithVersion = $runtimeKey;
|
||||
}
|
||||
}
|
||||
|
||||
$repo['runtime'] = $runtimeWithVersion ?? '';
|
||||
}
|
||||
}
|
||||
return $repo;
|
||||
};
|
||||
@@ -780,9 +933,9 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
}, $repos);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'providerRepositories' => $repos,
|
||||
$type === 'framework' ? 'frameworkProviderRepositories' : 'runtimeProviderRepositories' => $repos,
|
||||
'total' => \count($repos),
|
||||
]), Response::MODEL_PROVIDER_REPOSITORY_LIST);
|
||||
]), ($type === 'framework') ? Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST : Response::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST);
|
||||
});
|
||||
|
||||
App::post('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
|
||||
+106
-9
@@ -3,6 +3,7 @@
|
||||
require_once __DIR__ . '/../init.php';
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Auth\Key;
|
||||
use Appwrite\Event\Certificate;
|
||||
@@ -155,6 +156,80 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
$path .= '?' . $query;
|
||||
}
|
||||
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
/**
|
||||
Ensure preview authorization
|
||||
- Authorization is skippable for tests, and build screenshot
|
||||
- If cookie is not sent by client -> not authorized
|
||||
- If JWT in cookie is invalid or expired -> not authorized
|
||||
- If user is blocked or removed -> not authorized
|
||||
- If user's session is removed or expired -> not authorized
|
||||
- If user is not member of team of this deployment -> not authorized
|
||||
- If not authorized, redirect to Console redirect UI
|
||||
- If authorized, continue as if auth was not required
|
||||
*/
|
||||
$requirePreview = \is_null($apiKey) || !$apiKey->isPreviewAuthDisabled();
|
||||
if ($isPreview && $requirePreview) {
|
||||
$cookie = $request->getCookie(Auth::$cookieNamePreview, '');
|
||||
$authorized = false;
|
||||
|
||||
// Security checks to mark authorized true
|
||||
if (!empty($cookie)) {
|
||||
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
|
||||
|
||||
$payload = [];
|
||||
try {
|
||||
$payload = $jwt->decode($cookie);
|
||||
} catch (JWTException $error) {
|
||||
// Authorized remains false
|
||||
}
|
||||
|
||||
$userExists = false;
|
||||
$userId = $payload['userId'] ?? '';
|
||||
if (!empty($userId)) {
|
||||
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
if (!$user->isEmpty() && $user->getAttribute('status', false)) {
|
||||
$userExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
$sessionExists = false;
|
||||
$jwtSessionId = $payload['sessionId'] ?? '';
|
||||
if (!empty($jwtSessionId) && !empty($user->find('$id', $jwtSessionId, 'sessions'))) {
|
||||
$sessionExists = true;
|
||||
}
|
||||
|
||||
$membershipExists = false;
|
||||
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
|
||||
if (!$project->isEmpty()) {
|
||||
$teamId = $project->getAttribute('teamId', '');
|
||||
$membership = $user->find('teamId', $teamId, 'memberships');
|
||||
if (!empty($membership)) {
|
||||
$membershipExists = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($userExists && $sessionExists && $membershipExists) {
|
||||
$authorized = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$authorized) {
|
||||
$url = (System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https') . "://" . System::getEnv('_APP_DOMAIN');
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->redirect($url . '/console/auth/preview?'
|
||||
. \http_build_query([
|
||||
'projectId' => $projectId,
|
||||
'origin' => $protocol . '://' . $host,
|
||||
'path' => $path
|
||||
]));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$body = $swooleRequest->getContent() ?? '';
|
||||
$method = $swooleRequest->server['request_method'];
|
||||
|
||||
@@ -174,7 +249,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
};
|
||||
|
||||
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
|
||||
$spec = Config::getParam('runtime-specifications')[$resource->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
$spec = Config::getParam('specifications')[$resource->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
|
||||
$runtime = match ($type) {
|
||||
'function' => $runtimes[$resource->getAttribute('runtime')] ?? null,
|
||||
@@ -191,13 +266,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
throw new AppwriteException(AppwriteException::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $resource->getAttribute('runtime', '') . '" is not supported');
|
||||
}
|
||||
|
||||
/** Check if build has completed */
|
||||
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
|
||||
if ($build->isEmpty()) {
|
||||
throw new AppwriteException(AppwriteException::BUILD_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($build->getAttribute('status') !== 'ready') {
|
||||
if ($deployment->getAttribute('status') !== 'ready') {
|
||||
throw new AppwriteException(AppwriteException::BUILD_NOT_READY);
|
||||
}
|
||||
|
||||
@@ -388,7 +457,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
variables: $vars,
|
||||
timeout: $resource->getAttribute('timeout', 30),
|
||||
image: $runtime['image'],
|
||||
source: $build->getAttribute('path', ''),
|
||||
source: $deployment->getAttribute('buildPath', ''),
|
||||
entrypoint: $entrypoint,
|
||||
version: $version,
|
||||
path: $path,
|
||||
@@ -1277,6 +1346,34 @@ App::get('/v1/ping')
|
||||
$response->text('Pong!');
|
||||
});
|
||||
|
||||
// Preview authorization
|
||||
App::get('/_appwrite/authorize')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('previewHostname')
|
||||
->action(function (Request $request, Response $response, string $previewHostname) {
|
||||
|
||||
$host = $request->getHostname() ?? '';
|
||||
if (!empty($previewHostname)) {
|
||||
$host = $previewHostname;
|
||||
}
|
||||
|
||||
$referrer = $request->getReferer();
|
||||
$protocol = \parse_url($request->getOrigin($referrer), PHP_URL_SCHEME);
|
||||
|
||||
$jwt = $request->getParam('jwt', '');
|
||||
$path = $request->getParam('path', '');
|
||||
|
||||
$duration = 60 * 60 * 24; // 1 day in seconds
|
||||
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration));
|
||||
|
||||
$response
|
||||
->addCookie(Auth::$cookieNamePreview, $jwt, (new \DateTime($expire))->getTimestamp(), '/', $host, ('https' === $protocol), true, null)
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->redirect($protocol . '://' . $host . $path);
|
||||
});
|
||||
|
||||
App::wildcard()
|
||||
->groups(['api'])
|
||||
->label('scope', 'global')
|
||||
|
||||
+4
-5
@@ -36,13 +36,13 @@ use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Functions\Specification;
|
||||
use Appwrite\GraphQL\Promises\Adapter\Swoole;
|
||||
use Appwrite\GraphQL\Schema;
|
||||
use Appwrite\Hooks\Hooks;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\Platform\Modules\Compute\Specification;
|
||||
use Appwrite\PubSub\Adapter\Redis as PubSub;
|
||||
use Appwrite\URL\URL as AppwriteURL;
|
||||
use Appwrite\Utopia\Request;
|
||||
@@ -389,10 +389,9 @@ Config::load('storage-logos', __DIR__ . '/config/storage/logos.php');
|
||||
Config::load('storage-mimes', __DIR__ . '/config/storage/mimes.php');
|
||||
Config::load('storage-inputs', __DIR__ . '/config/storage/inputs.php');
|
||||
Config::load('storage-outputs', __DIR__ . '/config/storage/outputs.php');
|
||||
Config::load('runtime-specifications', __DIR__ . '/config/runtimes/specifications.php');
|
||||
Config::load('framework-specifications', __DIR__ . '/config/frameworks/specifications.php');
|
||||
Config::load('function-templates', __DIR__ . '/config/function-templates.php');
|
||||
Config::load('site-templates', __DIR__ . '/config/site-templates.php');
|
||||
Config::load('specifications', __DIR__ . '/config/specifications.php');
|
||||
Config::load('function-templates', __DIR__ . '/config/templates/function.php');
|
||||
Config::load('site-templates', __DIR__ . '/config/templates/site.php');
|
||||
|
||||
/**
|
||||
* New DB Filters
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"utopia-php/cli": "0.15.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "0.59.0",
|
||||
"utopia-php/detector": "0.1.*",
|
||||
"utopia-php/domains": "0.5.*",
|
||||
"utopia-php/dsn": "0.2.1",
|
||||
"utopia-php/framework": "0.33.*",
|
||||
|
||||
Generated
+46
-1
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "3b6171de8c624cfbcd723f1cc76a9560",
|
||||
"content-hash": "093587189a16826d640cab3e6ca5f251",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -3758,6 +3758,51 @@
|
||||
},
|
||||
"time": "2025-02-12T08:08:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/detector",
|
||||
"version": "0.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/detector.git",
|
||||
"reference": "ddeee9c3e702ae10b3eb53cafe5210a0c4896c94"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/detector/zipball/ddeee9c3e702ae10b3eb53cafe5210a0c4896c94",
|
||||
"reference": "ddeee9c3e702ae10b3eb53cafe5210a0c4896c94",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.2.*",
|
||||
"phpstan/phpstan": "1.8.*",
|
||||
"phpunit/phpunit": "^9.4"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Utopia\\Detector\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "A simple library for fast and reliable environment identification.",
|
||||
"keywords": [
|
||||
"detector",
|
||||
"framework",
|
||||
"php",
|
||||
"utopia"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/detector/issues",
|
||||
"source": "https://github.com/utopia-php/detector/tree/0.1.0"
|
||||
},
|
||||
"time": "2025-03-08T16:04:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/domains",
|
||||
"version": "0.5.0",
|
||||
|
||||
@@ -103,6 +103,11 @@ class Auth
|
||||
*/
|
||||
public static $cookieName = 'a_session';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public static $cookieNamePreview = 'a_jwt_console';
|
||||
|
||||
/**
|
||||
* User Unique ID.
|
||||
*
|
||||
|
||||
@@ -23,6 +23,7 @@ class Key
|
||||
protected bool $hostnameOverride = false,
|
||||
protected bool $bannerDisabled = false,
|
||||
protected bool $projectCheckDisabled = false,
|
||||
protected bool $previewAuthDisabled = false,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -73,6 +74,11 @@ class Key
|
||||
return $this->bannerDisabled;
|
||||
}
|
||||
|
||||
public function isPreviewAuthDisabled(): bool
|
||||
{
|
||||
return $this->previewAuthDisabled;
|
||||
}
|
||||
|
||||
public function isProjectCheckDisabled(): bool
|
||||
{
|
||||
return $this->projectCheckDisabled;
|
||||
@@ -132,6 +138,7 @@ class Key
|
||||
$hostnameOverride = $payload['hostnameOverride'] ?? false;
|
||||
$bannerDisabled = $payload['bannerDisabled'] ?? false;
|
||||
$projectCheckDisabled = $payload['projectCheckDisabled'] ?? false;
|
||||
$previewAuthDisabled = $payload['previewAuthDisabled'] ?? false;
|
||||
$scopes = \array_merge($payload['scopes'] ?? [], $scopes);
|
||||
|
||||
if (!$projectCheckDisabled && $projectId !== $project->getId()) {
|
||||
@@ -148,7 +155,8 @@ class Key
|
||||
$disabledMetrics,
|
||||
$hostnameOverride,
|
||||
$bannerDisabled,
|
||||
$projectCheckDisabled
|
||||
$projectCheckDisabled,
|
||||
$previewAuthDisabled
|
||||
);
|
||||
case API_KEY_STANDARD:
|
||||
$key = $project->find(
|
||||
|
||||
@@ -165,6 +165,7 @@ class Exception extends \Exception
|
||||
public const FUNCTION_ENTRYPOINT_MISSING = 'function_entrypoint_missing';
|
||||
public const FUNCTION_SYNCHRONOUS_TIMEOUT = 'function_synchronous_timeout';
|
||||
public const FUNCTION_TEMPLATE_NOT_FOUND = 'function_template_not_found';
|
||||
public const FUNCTION_RUNTIME_NOT_DETECTED = 'function_runtime_not_detected';
|
||||
|
||||
/** Deployments */
|
||||
public const DEPLOYMENT_NOT_FOUND = 'deployment_not_found';
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Functions;
|
||||
|
||||
class Specification
|
||||
{
|
||||
public const S_05VCPU_512MB = 's-0.5vcpu-512mb';
|
||||
public const S_1VCPU_512MB = 's-1vcpu-512mb';
|
||||
public const S_1VCPU_1GB = 's-1vcpu-1gb';
|
||||
public const S_2VCPU_2GB = 's-2vcpu-2gb';
|
||||
public const S_2VCPU_4GB = 's-2vcpu-4gb';
|
||||
public const S_4VCPU_4GB = 's-4vcpu-4gb';
|
||||
public const S_4VCPU_8GB = 's-4vcpu-8gb';
|
||||
public const S_8VCPU_4GB = 's-8vcpu-4gb';
|
||||
public const S_8VCPU_8GB = 's-8vcpu-8gb';
|
||||
}
|
||||
@@ -70,7 +70,7 @@ class Base extends Action
|
||||
'resourceInternalId' => $function->getInternalId(),
|
||||
'resourceType' => 'functions',
|
||||
'entrypoint' => $entrypoint,
|
||||
'commands' => $function->getAttribute('commands', ''),
|
||||
'buildCommands' => $function->getAttribute('commands', ''),
|
||||
'type' => 'vcs',
|
||||
'installationId' => $installation->getId(),
|
||||
'installationInternalId' => $installation->getInternalId(),
|
||||
@@ -139,6 +139,14 @@ class Base extends Action
|
||||
}
|
||||
}
|
||||
|
||||
$commands = [];
|
||||
if (!empty($site->getAttribute('buildCommand', ''))) {
|
||||
$commands[] = $site->getAttribute('buildCommand', '');
|
||||
}
|
||||
if (!empty($site->getAttribute('installCommand', ''))) {
|
||||
$commands[] = $site->getAttribute('installCommand', '');
|
||||
}
|
||||
|
||||
$deployment = $dbForProject->createDocument('deployments', new Document([
|
||||
'$id' => $deploymentId,
|
||||
'$permissions' => [
|
||||
@@ -149,9 +157,8 @@ class Base extends Action
|
||||
'resourceId' => $site->getId(),
|
||||
'resourceInternalId' => $site->getInternalId(),
|
||||
'resourceType' => 'sites',
|
||||
'buildCommand' => $site->getAttribute('buildCommand', ''),
|
||||
'installCommand' => $site->getAttribute('installCommand', ''),
|
||||
'outputDirectory' => $site->getAttribute('outputDirectory', ''),
|
||||
'buildCommands' => implode(' && ', $commands),
|
||||
'buildOutput' => $site->getAttribute('outputDirectory', ''),
|
||||
'type' => 'vcs',
|
||||
'installationId' => $installation->getId(),
|
||||
'installationInternalId' => $installation->getInternalId(),
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Sites;
|
||||
namespace Appwrite\Platform\Modules\Compute;
|
||||
|
||||
class Specification
|
||||
{
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Functions\Validator;
|
||||
namespace Appwrite\Platform\Modules\Compute\Validator;
|
||||
|
||||
use Utopia\Validator;
|
||||
|
||||
class RuntimeSpecification extends Validator
|
||||
class Specification extends Validator
|
||||
{
|
||||
private array $plan;
|
||||
|
||||
@@ -35,8 +35,8 @@ class RuntimeSpecification extends Validator
|
||||
|
||||
foreach ($this->specifications as $size => $values) {
|
||||
if ($values['cpus'] <= $this->maxCpus && $values['memory'] <= $this->maxMemory) {
|
||||
if (!empty($this->plan) && array_key_exists('runtimeSpecifications', $this->plan)) {
|
||||
if (!\in_array($size, $this->plan['runtimeSpecifications'])) {
|
||||
if (!empty($this->plan) && array_key_exists('specifications', $this->plan)) {
|
||||
if (!\in_array($size, $this->plan['specifications'])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -174,8 +174,8 @@ class Create extends Action
|
||||
|
||||
$metadata = ['content_type' => $deviceForLocal->getFileMimeType($fileTmpName)];
|
||||
if (!$deployment->isEmpty()) {
|
||||
$chunks = $deployment->getAttribute('chunksTotal', 1);
|
||||
$metadata = $deployment->getAttribute('metadata', []);
|
||||
$chunks = $deployment->getAttribute('sourceChunksTotal', 1);
|
||||
$metadata = $deployment->getAttribute('sourceMetadata', []);
|
||||
if ($chunk === -1) {
|
||||
$chunk = $chunks;
|
||||
}
|
||||
@@ -217,18 +217,17 @@ class Create extends Action
|
||||
'resourceInternalId' => $function->getInternalId(),
|
||||
'resourceId' => $function->getId(),
|
||||
'resourceType' => 'functions',
|
||||
'buildInternalId' => '',
|
||||
'entrypoint' => $entrypoint,
|
||||
'commands' => $commands,
|
||||
'path' => $path,
|
||||
'size' => $fileSize,
|
||||
'buildCommands' => $commands,
|
||||
'sourcePath' => $path,
|
||||
'sourceSize' => $fileSize,
|
||||
'search' => implode(' ', [$deploymentId, $entrypoint]),
|
||||
'activate' => $activate,
|
||||
'metadata' => $metadata,
|
||||
'sourceMetadata' => $metadata,
|
||||
'type' => $type
|
||||
]));
|
||||
} else {
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('size', $fileSize)->setAttribute('metadata', $metadata));
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('sourceSize', $fileSize)->setAttribute('sourceMetadata', $metadata));
|
||||
}
|
||||
|
||||
// Start the build
|
||||
@@ -248,20 +247,19 @@ class Create extends Action
|
||||
'resourceInternalId' => $function->getInternalId(),
|
||||
'resourceId' => $function->getId(),
|
||||
'resourceType' => 'functions',
|
||||
'buildInternalId' => '',
|
||||
'entrypoint' => $entrypoint,
|
||||
'commands' => $commands,
|
||||
'path' => $path,
|
||||
'size' => $fileSize,
|
||||
'chunksTotal' => $chunks,
|
||||
'chunksUploaded' => $chunksUploaded,
|
||||
'buildCommands' => $commands,
|
||||
'sourcePath' => $path,
|
||||
'sourceSize' => $fileSize,
|
||||
'sourceChunksTotal' => $chunks,
|
||||
'sourceChunksUploaded' => $chunksUploaded,
|
||||
'search' => implode(' ', [$deploymentId, $entrypoint]),
|
||||
'activate' => $activate,
|
||||
'metadata' => $metadata,
|
||||
'sourceMetadata' => $metadata,
|
||||
'type' => $type
|
||||
]));
|
||||
} else {
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('chunksUploaded', $chunksUploaded)->setAttribute('metadata', $metadata));
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('sourceChunksUploaded', $chunksUploaded)->setAttribute('sourceMetadata', $metadata));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,8 +83,8 @@ class Delete extends Action
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove deployment from DB');
|
||||
}
|
||||
|
||||
if (!empty($deployment->getAttribute('path', ''))) {
|
||||
if (!($deviceForFunctions->delete($deployment->getAttribute('path', '')))) {
|
||||
if (!empty($deployment->getAttribute('sourcePath', ''))) {
|
||||
if (!($deviceForFunctions->delete($deployment->getAttribute('sourcePath', '')))) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove deployment from storage');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,16 +81,11 @@ class Get extends Action
|
||||
|
||||
switch ($type) {
|
||||
case 'output':
|
||||
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId'));
|
||||
if ($build->isEmpty()) {
|
||||
throw new Exception(Exception::BUILD_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $build->getAttribute('path', '');
|
||||
$path = $deployment->getAttribute('buildPath', '');
|
||||
$device = $deviceForBuilds;
|
||||
break;
|
||||
case 'source':
|
||||
$path = $deployment->getAttribute('path', '');
|
||||
$path = $deployment->getAttribute('sourcePath', '');
|
||||
$device = $deviceForFunctions;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ class Create extends Action
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $deployment->getAttribute('path');
|
||||
$path = $deployment->getAttribute('sourcePath');
|
||||
if (empty($path) || !$deviceForFunctions->exists($path)) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
@@ -91,12 +91,17 @@ class Create extends Action
|
||||
$deployment = $dbForProject->createDocument('deployments', $deployment->setAttributes([
|
||||
'$internalId' => '',
|
||||
'$id' => $deploymentId,
|
||||
'buildId' => '',
|
||||
'buildInternalId' => '',
|
||||
'path' => $destination,
|
||||
'sourcePath' => $destination,
|
||||
'entrypoint' => $function->getAttribute('entrypoint'),
|
||||
'commands' => $function->getAttribute('commands', ''),
|
||||
'buildCommands' => $function->getAttribute('commands', ''),
|
||||
'search' => implode(' ', [$deploymentId, $function->getAttribute('entrypoint')]),
|
||||
'buildStartAt' => null,
|
||||
'buildEndAt' => null,
|
||||
'buildDuration' => null,
|
||||
'buildSize' => null,
|
||||
'status' => 'processing',
|
||||
'buildPath' => '',
|
||||
'buildLogs' => '',
|
||||
]));
|
||||
|
||||
$queueForBuilds
|
||||
|
||||
@@ -69,13 +69,6 @@ class Get extends Action
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
|
||||
$deployment->setAttribute('status', $build->getAttribute('status', 'waiting'));
|
||||
$deployment->setAttribute('buildLogs', $build->getAttribute('logs', ''));
|
||||
$deployment->setAttribute('buildTime', $build->getAttribute('duration', 0));
|
||||
$deployment->setAttribute('buildSize', $build->getAttribute('size', 0));
|
||||
$deployment->setAttribute('size', $deployment->getAttribute('size', 0));
|
||||
|
||||
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
@@ -50,7 +48,7 @@ class Update extends Action
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_BUILD,
|
||||
model: Response::MODEL_DEPLOYMENT,
|
||||
)
|
||||
]
|
||||
))
|
||||
@@ -77,46 +75,19 @@ class Update extends Action
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
|
||||
|
||||
if ($build->isEmpty()) {
|
||||
$buildId = ID::unique();
|
||||
$build = $dbForProject->createDocument('builds', new Document([
|
||||
'$id' => $buildId,
|
||||
'$permissions' => [],
|
||||
'startTime' => DateTime::now(),
|
||||
'deploymentInternalId' => $deployment->getInternalId(),
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'status' => 'canceled',
|
||||
'path' => '',
|
||||
'runtime' => $function->getAttribute('runtime'),
|
||||
'source' => $deployment->getAttribute('path', ''),
|
||||
'sourceType' => '',
|
||||
'logs' => '',
|
||||
'duration' => 0,
|
||||
'size' => 0
|
||||
]));
|
||||
|
||||
$deployment->setAttribute('buildId', $build->getId());
|
||||
$deployment->setAttribute('buildInternalId', $build->getInternalId());
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
} else {
|
||||
if (\in_array($build->getAttribute('status'), ['ready', 'failed'])) {
|
||||
throw new Exception(Exception::BUILD_ALREADY_COMPLETED);
|
||||
}
|
||||
|
||||
$startTime = new \DateTime($build->getAttribute('startTime'));
|
||||
$endTime = new \DateTime('now');
|
||||
$duration = $endTime->getTimestamp() - $startTime->getTimestamp();
|
||||
|
||||
$build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttributes([
|
||||
'endTime' => DateTime::now(),
|
||||
'duration' => $duration,
|
||||
'status' => 'canceled'
|
||||
]));
|
||||
if (\in_array($deployment->getAttribute('status'), ['ready', 'failed'])) {
|
||||
throw new Exception(Exception::BUILD_ALREADY_COMPLETED);
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('deployments', $deployment->getId());
|
||||
$startTime = new \DateTime($deployment->getAttribute('buildStartAt'));
|
||||
$endTime = new \DateTime('now');
|
||||
$duration = $endTime->getTimestamp() - $startTime->getTimestamp();
|
||||
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment->setAttributes([
|
||||
'buildEndAt' => DateTime::now(),
|
||||
'buildDuration' => $duration,
|
||||
'status' => 'canceled'
|
||||
]));
|
||||
|
||||
try {
|
||||
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
||||
@@ -132,6 +103,6 @@ class Update extends Action
|
||||
->setParam('functionId', $function->getId())
|
||||
->setParam('deploymentId', $deployment->getId());
|
||||
|
||||
$response->dynamic($build, Response::MODEL_BUILD);
|
||||
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ class Create extends Base
|
||||
'resourceInternalId' => $function->getInternalId(),
|
||||
'resourceType' => 'functions',
|
||||
'entrypoint' => $function->getAttribute('entrypoint', ''),
|
||||
'commands' => $function->getAttribute('commands', ''),
|
||||
'buildCommands' => $function->getAttribute('commands', ''),
|
||||
'type' => 'manual',
|
||||
'search' => implode(' ', [$deploymentId, $function->getAttribute('entrypoint', '')]),
|
||||
'activate' => $activate,
|
||||
|
||||
@@ -110,15 +110,6 @@ class XList extends Action
|
||||
$results = $dbForProject->find('deployments', $queries);
|
||||
$total = $dbForProject->count('deployments', $filterQueries, APP_LIMIT_COUNT);
|
||||
|
||||
foreach ($results as $result) {
|
||||
$build = $dbForProject->getDocument('builds', $result->getAttribute('buildId', ''));
|
||||
$result->setAttribute('status', $build->getAttribute('status', 'processing'));
|
||||
$result->setAttribute('buildLogs', $build->getAttribute('logs', ''));
|
||||
$result->setAttribute('buildTime', $build->getAttribute('duration', 0));
|
||||
$result->setAttribute('buildSize', $build->getAttribute('size', 0));
|
||||
$result->setAttribute('size', $result->getAttribute('size', 0));
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'deployments' => $results,
|
||||
'total' => $total,
|
||||
|
||||
@@ -4,8 +4,6 @@ namespace Appwrite\Platform\Modules\Functions\Http\Executions;
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
@@ -140,7 +138,7 @@ class Create extends Base
|
||||
|
||||
$version = $function->getAttribute('version', 'v2');
|
||||
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);
|
||||
$spec = Config::getParam('runtime-specifications')[$function->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
$spec = Config::getParam('specifications')[$function->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
|
||||
$runtime = (isset($runtimes[$function->getAttribute('runtime', '')])) ? $runtimes[$function->getAttribute('runtime', '')] : null;
|
||||
|
||||
@@ -158,13 +156,7 @@ class Create extends Base
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND, 'Deployment not found. Create a deployment before trying to execute a function');
|
||||
}
|
||||
|
||||
/** Check if build has completed */
|
||||
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
|
||||
if ($build->isEmpty()) {
|
||||
throw new Exception(Exception::BUILD_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($build->getAttribute('status') !== 'ready') {
|
||||
if ($deployment->getAttribute('status') !== 'ready') {
|
||||
throw new Exception(Exception::BUILD_NOT_READY);
|
||||
}
|
||||
|
||||
@@ -385,7 +377,7 @@ class Create extends Base
|
||||
variables: $vars,
|
||||
timeout: $function->getAttribute('timeout', 0),
|
||||
image: $runtime['image'],
|
||||
source: $build->getAttribute('path', ''),
|
||||
source: $deployment->getAttribute('buildPath', ''),
|
||||
entrypoint: $deployment->getAttribute('entrypoint', ''),
|
||||
version: $version,
|
||||
path: $path,
|
||||
|
||||
@@ -5,8 +5,8 @@ namespace Appwrite\Platform\Modules\Functions\Http\Functions;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Validator\FunctionEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Functions\Validator\RuntimeSpecification;
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\Platform\Modules\Compute\Validator\Specification;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -85,9 +85,9 @@ class Create extends Base
|
||||
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function.', true)
|
||||
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true)
|
||||
->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true)
|
||||
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification(
|
||||
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('runtime-specifications', []),
|
||||
Config::getParam('specifications', []),
|
||||
App::getEnv('_APP_COMPUTE_CPUS', APP_COMPUTE_CPUS_DEFAULT),
|
||||
App::getEnv('_APP_COMPUTE_MEMORY', APP_COMPUTE_MEMORY_DEFAULT)
|
||||
), 'Runtime specification for the function and builds.', true, ['plan'])
|
||||
|
||||
@@ -68,7 +68,6 @@ class Update extends Base
|
||||
{
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
throw new Exception(Exception::FUNCTION_NOT_FOUND);
|
||||
@@ -78,11 +77,7 @@ class Update extends Base
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($build->isEmpty()) {
|
||||
throw new Exception(Exception::BUILD_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($build->getAttribute('status') !== 'ready') {
|
||||
if ($deployment->getAttribute('status') !== 'ready') {
|
||||
throw new Exception(Exception::BUILD_NOT_READY);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Validator\FunctionEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Functions\Validator\RuntimeSpecification;
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\Platform\Modules\Compute\Validator\Specification;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -89,9 +89,9 @@ class Update extends Base
|
||||
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function', true)
|
||||
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the function? In silent mode, comments will not be made on commits and pull requests.', true)
|
||||
->param('providerRootDirectory', '', new Text(128, 0), 'Path to function code in the linked repo.', true)
|
||||
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new RuntimeSpecification(
|
||||
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('runtime-specifications', []),
|
||||
Config::getParam('specifications', []),
|
||||
App::getEnv('_APP_COMPUTE_CPUS', APP_COMPUTE_CPUS_DEFAULT),
|
||||
App::getEnv('_APP_COMPUTE_MEMORY', APP_COMPUTE_MEMORY_DEFAULT)
|
||||
), 'Runtime specification for the function and builds.', true, ['plan'])
|
||||
@@ -206,8 +206,6 @@ class Update extends Base
|
||||
$live = false;
|
||||
}
|
||||
|
||||
$spec = Config::getParam('runtime-specifications')[$specification] ?? [];
|
||||
|
||||
// Enforce Cold Start if spec limits change.
|
||||
if ($function->getAttribute('specification') !== $specification && !empty($function->getAttribute('deployment'))) {
|
||||
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
||||
|
||||
@@ -19,7 +19,7 @@ class XList extends Base
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'listFunctionsSpecifications';
|
||||
return 'listSpecifications';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
@@ -28,7 +28,7 @@ class XList extends Base
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/functions/specifications')
|
||||
->groups(['api', 'functions'])
|
||||
->desc('List available function runtime specifications')
|
||||
->desc('List specifications')
|
||||
->label('scope', 'functions.read')
|
||||
->label('resourceType', RESOURCE_TYPE_FUNCTIONS)
|
||||
->label('sdk', new Method(
|
||||
@@ -52,25 +52,25 @@ class XList extends Base
|
||||
|
||||
public function action(Response $response, array $plan)
|
||||
{
|
||||
$allRuntimeSpecs = Config::getParam('runtime-specifications', []);
|
||||
$allSpecs = Config::getParam('specifications', []);
|
||||
|
||||
$runtimeSpecs = [];
|
||||
foreach ($allRuntimeSpecs as $spec) {
|
||||
$specs = [];
|
||||
foreach ($allSpecs as $spec) {
|
||||
$spec['enabled'] = true;
|
||||
|
||||
if (array_key_exists('runtimeSpecifications', $plan)) {
|
||||
$spec['enabled'] = in_array($spec['slug'], $plan['runtimeSpecifications']);
|
||||
if (array_key_exists('specifications', $plan)) {
|
||||
$spec['enabled'] = in_array($spec['slug'], $plan['specifications']);
|
||||
}
|
||||
|
||||
// Only add specs that are within the limits set by environment variables
|
||||
if ($spec['cpus'] <= System::getEnv('_APP_COMPUTE_CPUS', 1) && $spec['memory'] <= System::getEnv('_APP_COMPUTE_MEMORY', 512)) {
|
||||
$runtimeSpecs[] = $spec;
|
||||
$specs[] = $spec;
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'specifications' => $runtimeSpecs,
|
||||
'total' => count($runtimeSpecs)
|
||||
'specifications' => $specs,
|
||||
'total' => count($specs)
|
||||
]), Response::MODEL_SPECIFICATION_LIST);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ use Utopia\Database\Exception\Structure;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Detector\Detection\Rendering\SSR;
|
||||
use Utopia\Detector\Detection\Rendering\XStatic;
|
||||
use Utopia\Detector\Detector\Rendering;
|
||||
use Utopia\Fetch\Client as FetchClient;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Platform\Action;
|
||||
@@ -173,7 +176,7 @@ class Builds extends Action
|
||||
$version = $this->getVersion($resource);
|
||||
$runtime = $this->getRuntime($resource, $version);
|
||||
|
||||
$spec = Config::getParam('runtime-specifications')[$resource->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
$spec = Config::getParam('specifications')[$resource->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
|
||||
if ($resource->getCollection() === 'functions' && \is_null($runtime)) {
|
||||
throw new \Exception('Runtime "' . $resource->getAttribute('runtime', '') . '" is not supported');
|
||||
@@ -187,39 +190,19 @@ class Builds extends Action
|
||||
|
||||
$startTime = DateTime::now();
|
||||
$durationStart = \microtime(true);
|
||||
$buildId = $deployment->getAttribute('buildId', '');
|
||||
$build = $dbForProject->getDocument('builds', $buildId);
|
||||
$isNewBuild = empty($buildId);
|
||||
if ($build->isEmpty()) {
|
||||
$buildId = ID::unique();
|
||||
$build = $dbForProject->createDocument('builds', new Document([
|
||||
'$id' => $buildId,
|
||||
'$permissions' => [],
|
||||
'startTime' => $startTime,
|
||||
'deploymentInternalId' => $deployment->getInternalId(),
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'status' => 'processing',
|
||||
'path' => '',
|
||||
'runtime' => $resource->getAttribute('runtime'),
|
||||
'source' => $deployment->getAttribute('path', ''),
|
||||
'sourceType' => strtolower($deviceForFunctions->getType()),
|
||||
'logs' => '',
|
||||
'endTime' => null,
|
||||
'duration' => 0,
|
||||
'size' => 0
|
||||
]));
|
||||
|
||||
$deployment->setAttribute('buildId', $build->getId());
|
||||
$deployment->setAttribute('buildInternalId', $build->getInternalId());
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
} elseif ($build->getAttribute('status') === 'canceled') {
|
||||
if ($deployment->getAttribute('status') === 'canceled') {
|
||||
Console::info('Build has been canceled');
|
||||
return;
|
||||
} else {
|
||||
$build = $dbForProject->getDocument('builds', $buildId);
|
||||
}
|
||||
|
||||
$source = $deployment->getAttribute('path', '');
|
||||
$deploymentId = $deployment->getId();
|
||||
|
||||
$deployment->setAttribute('buildStartAt', $startTime);
|
||||
$deployment->setAttribute('status', 'processing');
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
|
||||
$source = $deployment->getAttribute('sourcePath', '');
|
||||
$installationId = $deployment->getAttribute('installationId', '');
|
||||
$providerRepositoryId = $deployment->getAttribute('providerRepositoryId', '');
|
||||
$providerCommitHash = $deployment->getAttribute('providerCommitHash', '');
|
||||
@@ -237,7 +220,7 @@ class Builds extends Action
|
||||
}
|
||||
|
||||
try {
|
||||
if ($isNewBuild && !$isVcsEnabled) {
|
||||
if (!$isVcsEnabled) {
|
||||
// Non-VCS + Template
|
||||
$templateRepositoryName = $template->getAttribute('repositoryName', '');
|
||||
$templateOwnerName = $template->getAttribute('ownerName', '');
|
||||
@@ -253,7 +236,7 @@ class Builds extends Action
|
||||
$stderr = '';
|
||||
|
||||
// Clone template repo
|
||||
$tmpTemplateDirectory = '/tmp/builds/' . $buildId . '-template';
|
||||
$tmpTemplateDirectory = '/tmp/builds/' . $deploymentId . '-template';
|
||||
$gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateVersion, GitHub::CLONE_TYPE_TAG, $tmpTemplateDirectory, $templateRootDirectory);
|
||||
$exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr);
|
||||
|
||||
@@ -287,12 +270,14 @@ class Builds extends Action
|
||||
Console::execute('rm -rf ' . \escapeshellarg($tmpTemplateDirectory), '', $stdout, $stderr);
|
||||
|
||||
$directorySize = $device->getFileSize($source);
|
||||
$build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source));
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment->setAttribute('path', $source)->setAttribute('size', $directorySize));
|
||||
$deployment
|
||||
->setAttribute('sourcePath', $source)
|
||||
->setAttribute('sourceSize', $directorySize);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
}
|
||||
} elseif ($isNewBuild && $isVcsEnabled) {
|
||||
} elseif ($isVcsEnabled) {
|
||||
// VCS and VCS+Temaplte
|
||||
$tmpDirectory = '/tmp/builds/' . $buildId . '/code';
|
||||
$tmpDirectory = '/tmp/builds/' . $deploymentId . '/code';
|
||||
$rootDirectory = $resource->getAttribute('providerRootDirectory', '');
|
||||
$rootDirectory = \rtrim($rootDirectory, '/');
|
||||
$rootDirectory = \ltrim($rootDirectory, '.');
|
||||
@@ -318,9 +303,9 @@ class Builds extends Action
|
||||
$stdout = '';
|
||||
$stderr = '';
|
||||
|
||||
Console::execute('mkdir -p ' . \escapeshellarg('/tmp/builds/' . $buildId), '', $stdout, $stderr);
|
||||
Console::execute('mkdir -p ' . \escapeshellarg('/tmp/builds/' . $deploymentId), '', $stdout, $stderr);
|
||||
|
||||
if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') {
|
||||
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
|
||||
Console::info('Build has been canceled');
|
||||
return;
|
||||
}
|
||||
@@ -357,7 +342,7 @@ class Builds extends Action
|
||||
|
||||
if (!empty($templateRepositoryName) && !empty($templateOwnerName) && !empty($templateVersion)) {
|
||||
// Clone template repo
|
||||
$tmpTemplateDirectory = '/tmp/builds/' . $buildId . '/template';
|
||||
$tmpTemplateDirectory = '/tmp/builds/' . $deploymentId . '/template';
|
||||
|
||||
$gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateVersion, GitHub::CLONE_TYPE_TAG, $tmpTemplateDirectory, $templateRootDirectory);
|
||||
$exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr);
|
||||
@@ -402,19 +387,19 @@ class Builds extends Action
|
||||
$target = Realtime::fromPayload(
|
||||
// Pass first, most verbose event pattern
|
||||
event: $allEvents[0],
|
||||
payload: $build,
|
||||
payload: $deployment,
|
||||
project: $project
|
||||
);
|
||||
Realtime::send(
|
||||
projectId: 'console',
|
||||
payload: $build->getArrayCopy(),
|
||||
payload: $deployment->getArrayCopy(),
|
||||
events: $allEvents,
|
||||
channels: $target['channels'],
|
||||
roles: $target['roles']
|
||||
);
|
||||
}
|
||||
|
||||
$tmpPath = '/tmp/builds/' . $buildId;
|
||||
$tmpPath = '/tmp/builds/' . $deployment;
|
||||
$tmpPathFile = $tmpPath . '/code.tar.gz';
|
||||
$localDevice = new Local();
|
||||
|
||||
@@ -431,7 +416,7 @@ class Builds extends Action
|
||||
|
||||
Console::execute('find ' . \escapeshellarg($tmpDirectory) . ' -type d -name ".git" -exec rm -rf {} +', '', $stdout, $stderr);
|
||||
|
||||
$tarParamDirectory = '/tmp/builds/' . $buildId . '/code' . (empty($rootDirectory) ? '' : '/' . $rootDirectory);
|
||||
$tarParamDirectory = '/tmp/builds/' . $deploymentId . '/code' . (empty($rootDirectory) ? '' : '/' . $rootDirectory);
|
||||
Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $stdout, $stderr); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax
|
||||
|
||||
$source = $device->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
|
||||
@@ -443,17 +428,19 @@ class Builds extends Action
|
||||
|
||||
Console::execute('rm -rf ' . \escapeshellarg($tmpPath), '', $stdout, $stderr);
|
||||
|
||||
$build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source));
|
||||
|
||||
$directorySize = $device->getFileSize($source);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment->setAttribute('path', $source)->setAttribute('size', $directorySize));
|
||||
|
||||
$deployment
|
||||
->setAttribute('sourcePath', $source)
|
||||
->setAttribute('sourceSize', $directorySize);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
|
||||
$this->runGitAction('processing', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform);
|
||||
}
|
||||
|
||||
/** Request the executor to build the code... */
|
||||
$build->setAttribute('status', 'building');
|
||||
$build = $dbForProject->updateDocument('builds', $buildId, $build);
|
||||
$deployment->setAttribute('status', 'building');
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
|
||||
if ($isVcsEnabled) {
|
||||
$this->runGitAction('building', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform);
|
||||
@@ -482,13 +469,13 @@ class Builds extends Action
|
||||
$target = Realtime::fromPayload(
|
||||
// Pass first, most verbose event pattern
|
||||
event: $allEvents[0],
|
||||
payload: $build,
|
||||
payload: $deployment,
|
||||
project: $project
|
||||
);
|
||||
|
||||
Realtime::send(
|
||||
projectId: 'console',
|
||||
payload: $build->getArrayCopy(),
|
||||
payload: $deployment->getArrayCopy(),
|
||||
events: $allEvents,
|
||||
channels: $target['channels'],
|
||||
roles: $target['roles']
|
||||
@@ -583,7 +570,7 @@ class Builds extends Action
|
||||
$response = null;
|
||||
$err = null;
|
||||
|
||||
if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') {
|
||||
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
|
||||
Console::info('Build has been canceled');
|
||||
return;
|
||||
}
|
||||
@@ -605,7 +592,7 @@ class Builds extends Action
|
||||
cpus: $cpus,
|
||||
memory: $memory,
|
||||
timeout: $timeout,
|
||||
remove: true,
|
||||
remove: false,
|
||||
entrypoint: $deployment->getAttribute('entrypoint', ''),
|
||||
destination: APP_STORAGE_BUILDS . "/app-{$project->getId()}",
|
||||
variables: $vars,
|
||||
@@ -616,26 +603,22 @@ class Builds extends Action
|
||||
$err = $error;
|
||||
}
|
||||
}),
|
||||
Co\go(function () use ($executor, $project, $deployment, &$response, &$build, $dbForProject, $allEvents, $timeout, &$err, &$isCanceled) {
|
||||
Co\go(function () use ($executor, $project, &$deployment, &$response, $dbForProject, $allEvents, $timeout, &$err, &$isCanceled) {
|
||||
try {
|
||||
$executor->getLogs(
|
||||
deploymentId: $deployment->getId(),
|
||||
projectId: $project->getId(),
|
||||
timeout: $timeout,
|
||||
callback: function ($logs) use (&$response, &$err, &$build, $dbForProject, $allEvents, $project, &$isCanceled) {
|
||||
callback: function ($logs) use (&$response, &$err, $dbForProject, $allEvents, $project, &$isCanceled, &$deployment) {
|
||||
if ($isCanceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have response or error from concurrent coroutine, we already have latest logs
|
||||
if ($response === null && $err === null) {
|
||||
$build = $dbForProject->getDocument('builds', $build->getId());
|
||||
$deployment = $dbForProject->getDocument('deployments', $deployment->getId());
|
||||
|
||||
if ($build->isEmpty()) {
|
||||
throw new \Exception('Build not found');
|
||||
}
|
||||
|
||||
if ($build->getAttribute('status') === 'canceled') {
|
||||
if ($deployment->getAttribute('status') === 'canceled') {
|
||||
$isCanceled = true;
|
||||
Console::info('Ignoring realtime logs because build has been canceled');
|
||||
return;
|
||||
@@ -644,7 +627,7 @@ class Builds extends Action
|
||||
// Get only valid UTF8 part - removes leftover half-multibytes causing SQL errors
|
||||
$logs = \mb_substr($logs, 0, null, 'UTF-8');
|
||||
|
||||
$currentLogs = $build->getAttribute('logs', '');
|
||||
$currentLogs = $deployment->getAttribute('buildLogs', '');
|
||||
|
||||
$streamLogs = \str_replace("\\n", "{APPWRITE_LINEBREAK_PLACEHOLDER}", $logs);
|
||||
foreach (\explode("\n", $streamLogs) as $streamLog) {
|
||||
@@ -659,8 +642,8 @@ class Builds extends Action
|
||||
$currentLogs .= $streamParts[1];
|
||||
}
|
||||
|
||||
$build = $build->setAttribute('logs', $currentLogs);
|
||||
$build = $dbForProject->updateDocument('builds', $build->getId(), $build);
|
||||
$deployment = $deployment->setAttribute('buildLogs', $currentLogs);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
|
||||
/**
|
||||
* Send realtime Event
|
||||
@@ -668,12 +651,12 @@ class Builds extends Action
|
||||
$target = Realtime::fromPayload(
|
||||
// Pass first, most verbose event pattern
|
||||
event: $allEvents[0],
|
||||
payload: $build,
|
||||
payload: $deployment,
|
||||
project: $project
|
||||
);
|
||||
Realtime::send(
|
||||
projectId: 'console',
|
||||
payload: $build->getArrayCopy(),
|
||||
payload: $deployment->getArrayCopy(),
|
||||
events: $allEvents,
|
||||
channels: $target['channels'],
|
||||
roles: $target['roles']
|
||||
@@ -689,7 +672,7 @@ class Builds extends Action
|
||||
}),
|
||||
]);
|
||||
|
||||
if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') {
|
||||
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
|
||||
Console::info('Build has been canceled');
|
||||
return;
|
||||
}
|
||||
@@ -706,27 +689,55 @@ class Builds extends Action
|
||||
throw new \Exception('Build size should be less than ' . number_format($buildSizeLimit / 1048576, 2) . ' MBs.');
|
||||
}
|
||||
|
||||
if ($resource->getCollection() === 'sites' && empty($resource->getAttribute('adapter'))) {
|
||||
// TODO: Refactor with structured command in future, using utopia library (CLI)
|
||||
$listFilesCommand = "cd /usr/local/build && cd " . \escapeshellarg($resource->getAttribute('outputDirectory')) . " && find . -name 'node_modules' -prune -o -type f -print";
|
||||
$command = $executor->createCommand(
|
||||
deploymentId: $deployment->getId(),
|
||||
projectId: $project->getId(),
|
||||
command: $listFilesCommand,
|
||||
timeout: 15
|
||||
);
|
||||
|
||||
$files = \explode("\n", $command['output']); // Parse output
|
||||
$files = \array_filter($files); // Remove empty
|
||||
$files = \array_map(fn ($file) => \trim($file), $files); // Remove whitepsaces
|
||||
$files = \array_map(fn ($file) => \str_starts_with($file, './') ? \substr($file, 2) : $file, $files); // Remove beginning ./
|
||||
|
||||
$detector = new Rendering($files, $resource->getAttribute('framework', ''));
|
||||
$detector
|
||||
->addOption(new SSR())
|
||||
->addOption(new XStatic());
|
||||
$detection = $detector->detect();
|
||||
|
||||
$resource->setAttribute('adapter', $detection->getName());
|
||||
$resource->setAttribute('fallbackFile', $detection->getFallbackFile() ?? '');
|
||||
$resource = $dbForProject->updateDocument('sites', $resource->getId(), $resource);
|
||||
}
|
||||
|
||||
$executor->deleteRuntime($project->getId(), $deployment->getId(), '-build');
|
||||
|
||||
/** Update the build document */
|
||||
$build->setAttribute('startTime', DateTime::format((new \DateTime())->setTimestamp(floor($response['startTime']))));
|
||||
$build->setAttribute('endTime', $endTime);
|
||||
$build->setAttribute('duration', \intval(\ceil($durationEnd - $durationStart)));
|
||||
$build->setAttribute('status', 'ready');
|
||||
$build->setAttribute('path', $response['path']);
|
||||
$build->setAttribute('size', $response['size']);
|
||||
$deployment->setAttribute('buildStartAt', DateTime::format((new \DateTime())->setTimestamp(floor($response['startTime']))));
|
||||
$deployment->setAttribute('buildEndAt', $endTime);
|
||||
$deployment->setAttribute('buildDuration', \intval(\ceil($durationEnd - $durationStart)));
|
||||
$deployment->setAttribute('status', 'ready');
|
||||
$deployment->setAttribute('buildPath', $response['path']);
|
||||
$deployment->setAttribute('buildSize', $response['size']);
|
||||
|
||||
$logs = '';
|
||||
foreach ($response['output'] as $log) {
|
||||
$logs .= $log['content'];
|
||||
}
|
||||
$build->setAttribute('logs', $logs);
|
||||
$deployment->setAttribute('buildLogs', $logs);
|
||||
|
||||
$build = $dbForProject->updateDocument('builds', $buildId, $build);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment);
|
||||
|
||||
if ($isVcsEnabled) {
|
||||
$this->runGitAction('ready', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform);
|
||||
}
|
||||
|
||||
Console::success("Build id: $buildId created");
|
||||
Console::success("Build id: $deploymentId created");
|
||||
|
||||
if ($resource->getCollection() === 'sites') {
|
||||
try {
|
||||
@@ -763,7 +774,8 @@ class Builds extends Action
|
||||
$apiKey = $jwtObj->encode([
|
||||
'hostnameOverride' => true,
|
||||
'bannerDisabled' => true,
|
||||
'projectCheckDisabled' => true
|
||||
'projectCheckDisabled' => true,
|
||||
'previewAuthDisabled' => true,
|
||||
]);
|
||||
|
||||
// TODO: @Meldiron if becomes too slow, do concurrently
|
||||
@@ -946,7 +958,7 @@ class Builds extends Action
|
||||
}
|
||||
}
|
||||
|
||||
if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') {
|
||||
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
|
||||
Console::info('Build has been canceled');
|
||||
return;
|
||||
}
|
||||
@@ -963,20 +975,19 @@ class Builds extends Action
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
if ($dbForProject->getDocument('builds', $buildId)->getAttribute('status') === 'canceled') {
|
||||
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
|
||||
Console::info('Build has been canceled');
|
||||
return;
|
||||
}
|
||||
|
||||
$endTime = DateTime::now();
|
||||
$durationEnd = \microtime(true);
|
||||
$build->setAttribute('endTime', $endTime);
|
||||
$build->setAttribute('duration', \intval(\ceil($durationEnd - $durationStart)));
|
||||
$build->setAttribute('status', 'failed');
|
||||
$deployment->setAttribute('buildEndAt', $endTime);
|
||||
$deployment->setAttribute('buildDuration', \intval(\ceil($durationEnd - $durationStart)));
|
||||
$deployment->setAttribute('status', 'failed');
|
||||
$deployment->setAttribute('buildLogs', "[31m" . $th->getMessage());
|
||||
|
||||
$build->setAttribute('logs', "[31m" . $th->getMessage() . "[0m");
|
||||
|
||||
$build = $dbForProject->updateDocument('builds', $buildId, $build);
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment);
|
||||
|
||||
if ($isVcsEnabled) {
|
||||
$this->runGitAction('failed', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform);
|
||||
@@ -988,26 +999,26 @@ class Builds extends Action
|
||||
$target = Realtime::fromPayload(
|
||||
// Pass first, most verbose event pattern
|
||||
event: $allEvents[0],
|
||||
payload: $build,
|
||||
payload: $deployment,
|
||||
project: $project
|
||||
);
|
||||
Realtime::send(
|
||||
projectId: 'console',
|
||||
payload: $build->getArrayCopy(),
|
||||
payload: $deployment->getArrayCopy(),
|
||||
events: $allEvents,
|
||||
channels: $target['channels'],
|
||||
roles: $target['roles']
|
||||
);
|
||||
$this->sendUsage(
|
||||
resource:$resource,
|
||||
build: $build,
|
||||
deployment: $deployment,
|
||||
project: $project,
|
||||
queue: $queueForStatsUsage
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function sendUsage(Document $resource, Document $build, Document $project, StatsUsage $queue): void
|
||||
protected function sendUsage(Document $resource, Document $deployment, Document $project, StatsUsage $queue): void
|
||||
{
|
||||
$key = match($resource->getCollection()) {
|
||||
'functions' => 'functionInternalId',
|
||||
@@ -1038,32 +1049,32 @@ class Builds extends Action
|
||||
]
|
||||
};
|
||||
|
||||
switch ($build->getAttribute('status')) {
|
||||
switch ($deployment->getAttribute('status')) {
|
||||
case 'ready':
|
||||
$queue
|
||||
->addMetric(METRIC_BUILDS_SUCCESS, 1) // per project
|
||||
->addMetric(METRIC_BUILDS_COMPUTE_SUCCESS, (int)$build->getAttribute('duration', 0) * 1000)
|
||||
->addMetric(METRIC_BUILDS_COMPUTE_SUCCESS, (int)$deployment->getAttribute('buildDuration', 0) * 1000)
|
||||
->addMetric(str_replace($key, $resource->getInternalId(), METRIC_FUNCTION_ID_BUILDS_SUCCESS), 1) // per function
|
||||
->addMetric(str_replace($key, $resource->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE_SUCCESS), (int)$build->getAttribute('duration', 0) * 1000);
|
||||
->addMetric(str_replace($key, $resource->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE_SUCCESS), (int)$deployment->getAttribute('buildDuration', 0) * 1000);
|
||||
break;
|
||||
case 'failed':
|
||||
$queue
|
||||
->addMetric(METRIC_BUILDS_FAILED, 1) // per project
|
||||
->addMetric(METRIC_BUILDS_COMPUTE_FAILED, (int)$build->getAttribute('duration', 0) * 1000)
|
||||
->addMetric(METRIC_BUILDS_COMPUTE_FAILED, (int)$deployment->getAttribute('buildDuration', 0) * 1000)
|
||||
->addMetric(str_replace($key, $resource->getInternalId(), $metrics['buildsFailed']), 1) // per function
|
||||
->addMetric(str_replace($key, $resource->getInternalId(), $metrics['buildsComputeFailed']), (int)$build->getAttribute('duration', 0) * 1000);
|
||||
->addMetric(str_replace($key, $resource->getInternalId(), $metrics['buildsComputeFailed']), (int)$deployment->getAttribute('buildDuration', 0) * 1000);
|
||||
break;
|
||||
}
|
||||
|
||||
$queue
|
||||
->addMetric(METRIC_BUILDS, 1) // per project
|
||||
->addMetric(METRIC_BUILDS_STORAGE, $build->getAttribute('size', 0))
|
||||
->addMetric(METRIC_BUILDS_COMPUTE, (int)$build->getAttribute('duration', 0) * 1000)
|
||||
->addMetric(METRIC_BUILDS_MB_SECONDS, (int)(($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT) * $build->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT)))
|
||||
->addMetric(METRIC_BUILDS_STORAGE, $deployment->getAttribute('buildSize', 0))
|
||||
->addMetric(METRIC_BUILDS_COMPUTE, (int)$deployment->getAttribute('buildDuration', 0) * 1000)
|
||||
->addMetric(METRIC_BUILDS_MB_SECONDS, (int)(($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT) * $deployment->getAttribute('buildDuration', 0) * ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT)))
|
||||
->addMetric(str_replace($key, $resource->getInternalId(), METRIC_FUNCTION_ID_BUILDS), 1) // per function
|
||||
->addMetric(str_replace($key, $resource->getInternalId(), METRIC_FUNCTION_ID_BUILDS_STORAGE), $build->getAttribute('size', 0))
|
||||
->addMetric(str_replace($key, $resource->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE), (int)$build->getAttribute('duration', 0) * 1000)
|
||||
->addMetric(str_replace($key, $resource->getInternalId(), METRIC_FUNCTION_ID_BUILDS_MB_SECONDS), (int)(($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT) * $build->getAttribute('duration', 0) * ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT)))
|
||||
->addMetric(str_replace($key, $resource->getInternalId(), METRIC_FUNCTION_ID_BUILDS_STORAGE), $deployment->getAttribute('buildSize', 0))
|
||||
->addMetric(str_replace($key, $resource->getInternalId(), METRIC_FUNCTION_ID_BUILDS_COMPUTE), (int)$deployment->getAttribute('buildDuration', 0) * 1000)
|
||||
->addMetric(str_replace($key, $resource->getInternalId(), METRIC_FUNCTION_ID_BUILDS_MB_SECONDS), (int)(($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT) * $deployment->getAttribute('buildDuration', 0) * ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT)))
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
}
|
||||
@@ -1095,7 +1106,7 @@ class Builds extends Action
|
||||
protected function getCommand(Document $resource, Document $deployment): string
|
||||
{
|
||||
if ($resource->getCollection() === 'functions') {
|
||||
return $deployment->getAttribute('commands', '');
|
||||
return $deployment->getAttribute('buildCommands', '');
|
||||
} elseif ($resource->getCollection() === 'sites') {
|
||||
$commands = [];
|
||||
|
||||
@@ -1104,20 +1115,13 @@ class Builds extends Action
|
||||
|
||||
$envCommand = '';
|
||||
$bundleCommand = '';
|
||||
|
||||
if (!is_null($framework)) {
|
||||
$adapter = ($framework['adapters'] ?? [])[$resource->getAttribute('adapter', '')] ?? null;
|
||||
if (!is_null($adapter) && isset($adapter['envCommand'])) {
|
||||
$envCommand = $adapter['envCommand'];
|
||||
}
|
||||
if (!is_null($adapter) && isset($adapter['bundleCommand'])) {
|
||||
$bundleCommand = $adapter['bundleCommand'];
|
||||
}
|
||||
$envCommand = $framework['envCommand'] ?? '';
|
||||
$bundleCommand = $framework['bundleCommand'] ?? '';
|
||||
}
|
||||
|
||||
$commands[] = $envCommand;
|
||||
$commands[] = $deployment->getAttribute('installCommand', '');
|
||||
$commands[] = $deployment->getAttribute('buildCommand', '');
|
||||
$commands[] = $deployment->getAttribute('buildCommands', '');
|
||||
$commands[] = $bundleCommand;
|
||||
|
||||
$commands = array_filter($commands, fn ($command) => !empty($command));
|
||||
|
||||
@@ -173,8 +173,8 @@ class Create extends Action
|
||||
|
||||
$metadata = ['content_type' => $deviceForLocal->getFileMimeType($fileTmpName)];
|
||||
if (!$deployment->isEmpty()) {
|
||||
$chunks = $deployment->getAttribute('chunksTotal', 1);
|
||||
$metadata = $deployment->getAttribute('metadata', []);
|
||||
$chunks = $deployment->getAttribute('sourceChunksTotal', 1);
|
||||
$metadata = $deployment->getAttribute('sourceMetadata', []);
|
||||
if ($chunk === -1) {
|
||||
$chunk = $chunks;
|
||||
}
|
||||
@@ -188,6 +188,14 @@ class Create extends Action
|
||||
|
||||
$type = $request->getHeader('x-sdk-language') === 'cli' ? 'cli' : 'manual';
|
||||
|
||||
$commands = [];
|
||||
if (!empty($installCommand)) {
|
||||
$commands[] = $installCommand;
|
||||
}
|
||||
if (!empty($buildCommand)) {
|
||||
$commands[] = $buildCommand;
|
||||
}
|
||||
|
||||
if ($chunksUploaded === $chunks) {
|
||||
if ($activate) {
|
||||
// Remove deploy for all other deployments.
|
||||
@@ -216,15 +224,13 @@ class Create extends Action
|
||||
'resourceInternalId' => $site->getInternalId(),
|
||||
'resourceId' => $site->getId(),
|
||||
'resourceType' => 'sites',
|
||||
'buildInternalId' => '',
|
||||
'installCommand' => $installCommand,
|
||||
'buildCommand' => $buildCommand,
|
||||
'outputDirectory' => $outputDirectory,
|
||||
'path' => $path,
|
||||
'size' => $fileSize,
|
||||
'buildCommands' => \implode(' && ', $commands),
|
||||
'buildOutput' => $outputDirectory,
|
||||
'sourcePath' => $path,
|
||||
'sourceSize' => $fileSize,
|
||||
'search' => implode(' ', [$deploymentId]),
|
||||
'activate' => $activate,
|
||||
'metadata' => $metadata,
|
||||
'sourceMetadata' => $metadata,
|
||||
'type' => $type
|
||||
]));
|
||||
|
||||
@@ -253,7 +259,7 @@ class Create extends Action
|
||||
]))
|
||||
);
|
||||
} else {
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('size', $fileSize)->setAttribute('metadata', $metadata));
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('sourceSize', $fileSize)->setAttribute('sourceMetadata', $metadata));
|
||||
}
|
||||
|
||||
// Start the build
|
||||
@@ -273,17 +279,15 @@ class Create extends Action
|
||||
'resourceInternalId' => $site->getInternalId(),
|
||||
'resourceId' => $site->getId(),
|
||||
'resourceType' => 'sites',
|
||||
'buildInternalId' => '',
|
||||
'installCommand' => $installCommand,
|
||||
'buildCommand' => $buildCommand,
|
||||
'outputDirectory' => $outputDirectory,
|
||||
'path' => $path,
|
||||
'size' => $fileSize,
|
||||
'chunksTotal' => $chunks,
|
||||
'chunksUploaded' => $chunksUploaded,
|
||||
'buildCommands' => \implode(' && ', $commands),
|
||||
'buildOutput' => $outputDirectory,
|
||||
'sourcePath' => $path,
|
||||
'sourceSize' => $fileSize,
|
||||
'sourceChunksTotal' => $chunks,
|
||||
'sourceChunksUploaded' => $chunksUploaded,
|
||||
'search' => implode(' ', [$deploymentId]),
|
||||
'activate' => $activate,
|
||||
'metadata' => $metadata,
|
||||
'sourceMetadata' => $metadata,
|
||||
'type' => $type
|
||||
]));
|
||||
|
||||
@@ -305,7 +309,7 @@ class Create extends Action
|
||||
]))
|
||||
);
|
||||
} else {
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('chunksUploaded', $chunksUploaded)->setAttribute('metadata', $metadata));
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deploymentId, $deployment->setAttribute('sourceChunksUploaded', $chunksUploaded)->setAttribute('sourceMetadata', $metadata));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,8 +83,8 @@ class Delete extends Action
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove deployment from DB');
|
||||
}
|
||||
|
||||
if (!empty($deployment->getAttribute('path', ''))) {
|
||||
if (!($deviceForSites->delete($deployment->getAttribute('path', '')))) {
|
||||
if (!empty($deployment->getAttribute('sourcePath', ''))) {
|
||||
if (!($deviceForSites->delete($deployment->getAttribute('sourcePath', '')))) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove deployment from storage');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,16 +80,11 @@ class Get extends Action
|
||||
|
||||
switch ($type) {
|
||||
case 'output':
|
||||
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId'));
|
||||
if ($build->isEmpty()) {
|
||||
throw new Exception(Exception::BUILD_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $build->getAttribute('path', '');
|
||||
$path = $deployment->getAttribute('buildPath', '');
|
||||
$device = $deviceForBuilds;
|
||||
break;
|
||||
case 'source':
|
||||
$path = $deployment->getAttribute('path', '');
|
||||
$path = $deployment->getAttribute('sourcePath', '');
|
||||
$device = $deviceForSites;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ class Create extends Action
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $deployment->getAttribute('path');
|
||||
$path = $deployment->getAttribute('sourcePath');
|
||||
if (empty($path) || !$deviceForSites->exists($path)) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
@@ -88,19 +88,31 @@ class Create extends Action
|
||||
$destination = $deviceForSites->getPath($deploymentId . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
|
||||
$deviceForSites->transfer($path, $destination, $deviceForSites);
|
||||
|
||||
$commands = [];
|
||||
if (!empty($site->getAttribute('buildCommand', ''))) {
|
||||
$commands[] = $site->getAttribute('buildCommand', '');
|
||||
}
|
||||
if (!empty($site->getAttribute('installCommand', ''))) {
|
||||
$commands[] = $site->getAttribute('installCommand', '');
|
||||
}
|
||||
|
||||
$deployment->removeAttribute('$internalId');
|
||||
$deployment = $dbForProject->createDocument('deployments', $deployment->setAttributes([
|
||||
'$internalId' => '',
|
||||
'$id' => $deploymentId,
|
||||
'buildId' => '',
|
||||
'buildInternalId' => '',
|
||||
'path' => $destination,
|
||||
'buildCommand' => $site->getAttribute('buildCommand', ''),
|
||||
'installCommand' => $site->getAttribute('installCommand', ''),
|
||||
'outputDirectory' => $site->getAttribute('outputDirectory', ''),
|
||||
'sourcePath' => $destination,
|
||||
'buildCommands' => \implode(' && ', $commands),
|
||||
'buildOutput' => $site->getAttribute('outputDirectory', ''),
|
||||
'search' => implode(' ', [$deploymentId]),
|
||||
'screenshotLight' => '',
|
||||
'screenshotDark' => ''
|
||||
'screenshotDark' => '',
|
||||
'buildStartAt' => null,
|
||||
'buildEndAt' => null,
|
||||
'buildDuration' => null,
|
||||
'buildSize' => null,
|
||||
'status' => 'processing',
|
||||
'buildPath' => '',
|
||||
'buildLogs' => '',
|
||||
]));
|
||||
|
||||
// Preview deployments for sites
|
||||
|
||||
@@ -69,13 +69,6 @@ class Get extends Action
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
|
||||
$deployment->setAttribute('status', $build->getAttribute('status', 'waiting'));
|
||||
$deployment->setAttribute('buildLogs', $build->getAttribute('logs', ''));
|
||||
$deployment->setAttribute('buildTime', $build->getAttribute('duration', 0));
|
||||
$deployment->setAttribute('buildSize', $build->getAttribute('size', 0));
|
||||
$deployment->setAttribute('size', $deployment->getAttribute('size', 0));
|
||||
|
||||
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ use Utopia\App;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
@@ -48,7 +46,7 @@ class Update extends Action
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_BUILD,
|
||||
model: Response::MODEL_DEPLOYMENT,
|
||||
)
|
||||
]
|
||||
))
|
||||
@@ -75,46 +73,19 @@ class Update extends Action
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
|
||||
|
||||
if ($build->isEmpty()) {
|
||||
$buildId = ID::unique();
|
||||
$build = $dbForProject->createDocument('builds', new Document([
|
||||
'$id' => $buildId,
|
||||
'$permissions' => [],
|
||||
'startTime' => DateTime::now(),
|
||||
'deploymentInternalId' => $deployment->getInternalId(),
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'status' => 'canceled',
|
||||
'path' => '',
|
||||
'runtime' => $site->getAttribute('framework'),
|
||||
'source' => $deployment->getAttribute('path', ''),
|
||||
'sourceType' => '',
|
||||
'logs' => '',
|
||||
'duration' => 0,
|
||||
'size' => 0
|
||||
]));
|
||||
|
||||
$deployment->setAttribute('buildId', $build->getId());
|
||||
$deployment->setAttribute('buildInternalId', $build->getInternalId());
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
|
||||
} else {
|
||||
if (\in_array($build->getAttribute('status'), ['ready', 'failed'])) {
|
||||
throw new Exception(Exception::BUILD_ALREADY_COMPLETED);
|
||||
}
|
||||
|
||||
$startTime = new \DateTime($build->getAttribute('startTime'));
|
||||
$endTime = new \DateTime('now');
|
||||
$duration = $endTime->getTimestamp() - $startTime->getTimestamp();
|
||||
|
||||
$build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttributes([
|
||||
'endTime' => DateTime::now(),
|
||||
'duration' => $duration,
|
||||
'status' => 'canceled'
|
||||
]));
|
||||
if (\in_array($deployment->getAttribute('status'), ['ready', 'failed'])) {
|
||||
throw new Exception(Exception::BUILD_ALREADY_COMPLETED);
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('deployments', $deployment->getId());
|
||||
$startTime = new \DateTime($deployment->getAttribute('buildStartAt'));
|
||||
$endTime = new \DateTime('now');
|
||||
$duration = $endTime->getTimestamp() - $startTime->getTimestamp();
|
||||
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment->setAttributes([
|
||||
'buildEndAt' => DateTime::now(),
|
||||
'buildDuration' => $duration,
|
||||
'status' => 'canceled'
|
||||
]));
|
||||
|
||||
try {
|
||||
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
||||
@@ -130,6 +101,6 @@ class Update extends Action
|
||||
->setParam('siteId', $site->getId())
|
||||
->setParam('deploymentId', $deployment->getId());
|
||||
|
||||
$response->dynamic($build, Response::MODEL_BUILD);
|
||||
$response->dynamic($deployment, Response::MODEL_DEPLOYMENT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +121,14 @@ class Create extends Base
|
||||
return;
|
||||
}
|
||||
|
||||
$commands = [];
|
||||
if (!empty($site->getAttribute('installCommand', ''))) {
|
||||
$commands[] = $site->getAttribute('installCommand', '');
|
||||
}
|
||||
if (!empty($site->getAttribute('buildCommand', ''))) {
|
||||
$commands[] = $site->getAttribute('buildCommand', '');
|
||||
}
|
||||
|
||||
$deploymentId = ID::unique();
|
||||
$deployment = $dbForProject->createDocument('deployments', new Document([
|
||||
'$id' => $deploymentId,
|
||||
@@ -132,9 +140,8 @@ class Create extends Base
|
||||
'resourceId' => $site->getId(),
|
||||
'resourceInternalId' => $site->getInternalId(),
|
||||
'resourceType' => 'sites',
|
||||
'installCommand' => $site->getAttribute('installCommand', ''),
|
||||
'buildCommand' => $site->getAttribute('buildCommand', ''),
|
||||
'outputDirectory' => $site->getAttribute('outputDirectory', ''),
|
||||
'buildCommands' => \implode(' && ', $commands),
|
||||
'buildOutput' => $site->getAttribute('outputDirectory', ''),
|
||||
'type' => 'manual',
|
||||
'search' => implode(' ', [$deploymentId]),
|
||||
'activate' => $activate,
|
||||
|
||||
@@ -110,15 +110,6 @@ class XList extends Action
|
||||
$results = $dbForProject->find('deployments', $queries);
|
||||
$total = $dbForProject->count('deployments', $filterQueries, APP_LIMIT_COUNT);
|
||||
|
||||
foreach ($results as $result) {
|
||||
$build = $dbForProject->getDocument('builds', $result->getAttribute('buildId', ''));
|
||||
$result->setAttribute('status', $build->getAttribute('status', 'processing'));
|
||||
$result->setAttribute('buildLogs', $build->getAttribute('logs', ''));
|
||||
$result->setAttribute('buildTime', $build->getAttribute('duration', 0));
|
||||
$result->setAttribute('buildSize', $build->getAttribute('size', 0));
|
||||
$result->setAttribute('size', $result->getAttribute('size', 0));
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'deployments' => $results,
|
||||
'total' => $total,
|
||||
|
||||
@@ -5,10 +5,10 @@ namespace Appwrite\Platform\Modules\Sites\Http\Sites;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\Platform\Modules\Compute\Validator\Specification;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Sites\Validator\FrameworkSpecification;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
@@ -77,9 +77,9 @@ class Create extends Base
|
||||
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the site.', true)
|
||||
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the site? In silent mode, comments will not be made on commits and pull requests.', true)
|
||||
->param('providerRootDirectory', '', new Text(128, 0), 'Path to site code in the linked repo.', true)
|
||||
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new FrameworkSpecification(
|
||||
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('framework-specifications', []),
|
||||
Config::getParam('specifications', []),
|
||||
App::getEnv('_APP_COMPUTE_CPUS', APP_COMPUTE_CPUS_DEFAULT),
|
||||
App::getEnv('_APP_COMPUTE_MEMORY', APP_COMPUTE_MEMORY_DEFAULT)
|
||||
), 'Framework specification for the site and builds.', true, ['plan'])
|
||||
@@ -91,7 +91,7 @@ class Create extends Base
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $siteId, string $name, string $framework, bool $enabled, int $timeout, string $installCommand, string $buildCommand, string $outputDirectory, string $buildRuntime, string $adapter, string $installationId, ?string $fallbackFile, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Database $dbForPlatform)
|
||||
public function action(string $siteId, string $name, string $framework, bool $enabled, int $timeout, string $installCommand, string $buildCommand, string $outputDirectory, string $buildRuntime, string $adapter, string $installationId, string $fallbackFile, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Database $dbForPlatform)
|
||||
{
|
||||
if (!empty($adapter)) {
|
||||
$configFramework = Config::getParam('frameworks')[$framework] ?? [];
|
||||
|
||||
@@ -65,7 +65,6 @@ class Update extends Base
|
||||
{
|
||||
$site = $dbForProject->getDocument('sites', $siteId);
|
||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
|
||||
|
||||
if ($site->isEmpty()) {
|
||||
throw new Exception(Exception::SITE_NOT_FOUND);
|
||||
@@ -75,11 +74,7 @@ class Update extends Base
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($build->isEmpty()) {
|
||||
throw new Exception(Exception::BUILD_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($build->getAttribute('status') !== 'ready') {
|
||||
if ($deployment->getAttribute('status') !== 'ready') {
|
||||
throw new Exception(Exception::BUILD_NOT_READY);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\Platform\Modules\Compute\Validator\Specification;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Sites\Validator\FrameworkSpecification;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Executor\Executor;
|
||||
use Utopia\App;
|
||||
@@ -81,9 +81,9 @@ class Update extends Base
|
||||
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the site.', true)
|
||||
->param('providerSilentMode', false, new Boolean(), 'Is the VCS (Version Control System) connection in silent mode for the repo linked to the site? In silent mode, comments will not be made on commits and pull requests.', true)
|
||||
->param('providerRootDirectory', '', new Text(128, 0), 'Path to site code in the linked repo.', true)
|
||||
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new FrameworkSpecification(
|
||||
->param('specification', APP_COMPUTE_SPECIFICATION_DEFAULT, fn (array $plan) => new Specification(
|
||||
$plan,
|
||||
Config::getParam('framework-specifications', []),
|
||||
Config::getParam('specifications', []),
|
||||
App::getEnv('_APP_COMPUTE_CPUS', APP_COMPUTE_CPUS_DEFAULT),
|
||||
App::getEnv('_APP_COMPUTE_MEMORY', APP_COMPUTE_MEMORY_DEFAULT)
|
||||
), 'Framework specification for the site and builds.', true, ['plan'])
|
||||
@@ -98,7 +98,7 @@ class Update extends Base
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(string $siteId, string $name, string $framework, bool $enabled, int $timeout, string $installCommand, string $buildCommand, string $outputDirectory, string $buildRuntime, string $adapter, ?string $fallbackFile, string $installationId, ?string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForPlatform, GitHub $github)
|
||||
public function action(string $siteId, string $name, string $framework, bool $enabled, int $timeout, string $installCommand, string $buildCommand, string $outputDirectory, string $buildRuntime, string $adapter, string $fallbackFile, string $installationId, ?string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Database $dbForPlatform, GitHub $github)
|
||||
{
|
||||
if (!empty($adapter)) {
|
||||
$configFramework = Config::getParam('frameworks')[$framework] ?? [];
|
||||
@@ -204,8 +204,6 @@ class Update extends Base
|
||||
$live = false;
|
||||
}
|
||||
|
||||
$spec = Config::getParam('framework-specifications')[$specification] ?? [];
|
||||
|
||||
// Enforce Cold Start if spec limits change.
|
||||
if ($site->getAttribute('specification') !== $specification && !empty($site->getAttribute('deploymentId'))) {
|
||||
$executor = new Executor(App::getEnv('_APP_EXECUTOR_HOST'));
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Sites\Http\Specifications;
|
||||
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
|
||||
class XList extends Base
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'listSpecifications';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/sites/specifications')
|
||||
->groups(['api', 'sites'])
|
||||
->desc('List specifications')
|
||||
->label('scope', 'sites.read')
|
||||
->label('resourceType', RESOURCE_TYPE_SITES)
|
||||
->label('sdk', new Method(
|
||||
namespace: 'sites',
|
||||
name: 'listSpecifications',
|
||||
description: <<<EOT
|
||||
List allowed site specifications for this instance.
|
||||
EOT,
|
||||
auth: [AuthType::KEY, AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_SPECIFICATION_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->inject('response')
|
||||
->inject('plan')
|
||||
->callback([$this, 'action']);
|
||||
}
|
||||
|
||||
public function action(Response $response, array $plan)
|
||||
{
|
||||
$allSpecs = Config::getParam('specifications', []);
|
||||
|
||||
$specs = [];
|
||||
foreach ($allSpecs as $spec) {
|
||||
$spec['enabled'] = true;
|
||||
|
||||
if (array_key_exists('specifications', $plan)) {
|
||||
$spec['enabled'] = in_array($spec['slug'], $plan['specifications']);
|
||||
}
|
||||
|
||||
// Only add specs that are within the limits set by environment variables
|
||||
if ($spec['cpus'] <= System::getEnv('_APP_COMPUTE_CPUS', 1) && $spec['memory'] <= System::getEnv('_APP_COMPUTE_MEMORY', 512)) {
|
||||
$specs[] = $spec;
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'specifications' => $specs,
|
||||
'total' => count($specs)
|
||||
]), Response::MODEL_SPECIFICATION_LIST);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ use Appwrite\Platform\Modules\Sites\Http\Sites\Deployment\Update as UpdateSiteDe
|
||||
use Appwrite\Platform\Modules\Sites\Http\Sites\Get as GetSite;
|
||||
use Appwrite\Platform\Modules\Sites\Http\Sites\Update as UpdateSite;
|
||||
use Appwrite\Platform\Modules\Sites\Http\Sites\XList as ListSites;
|
||||
use Appwrite\Platform\Modules\Sites\Http\Specifications\XList as ListSpecifications;
|
||||
use Appwrite\Platform\Modules\Sites\Http\Templates\Get as GetTemplate;
|
||||
use Appwrite\Platform\Modules\Sites\Http\Templates\XList as ListTemplates;
|
||||
use Appwrite\Platform\Modules\Sites\Http\Usage\Get as GetUsage;
|
||||
@@ -79,5 +80,7 @@ class Http extends Service
|
||||
// Usage
|
||||
$this->addAction(ListUsage::getName(), new ListUsage());
|
||||
$this->addAction(GetUsage::getName(), new GetUsage());
|
||||
|
||||
$this->addAction(ListSpecifications::getName(), new ListSpecifications());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class Screenshot extends Action
|
||||
$template = \array_shift($allowedTemplates);
|
||||
|
||||
if (empty($template)) {
|
||||
throw new \Exception("Template {$templateId} not found. Find correct ID in app/config/site-templates.php");
|
||||
throw new \Exception("Template {$templateId} not found. Find correct ID in app/config/templates/site.php");
|
||||
}
|
||||
|
||||
Console::info("Found: " . $template['name']);
|
||||
|
||||
@@ -785,9 +785,10 @@ class Deletes extends Action
|
||||
$deploymentIds = [];
|
||||
$this->deleteByGroup('deployments', [
|
||||
Query::equal('resourceInternalId', [$siteInternalId])
|
||||
], $dbForProject, function (Document $document) use ($project, $certificates, $deviceForSites, $deviceForFiles, $dbForPlatform, &$deploymentInternalIds) {
|
||||
], $dbForProject, function (Document $document) use ($project, $certificates, $deviceForSites, $deviceForBuilds, $deviceForFiles, $dbForPlatform, &$deploymentInternalIds) {
|
||||
$deploymentInternalIds[] = $document->getInternalId();
|
||||
$deploymentIds[] = $document->getId();
|
||||
$this->deleteBuildFiles($deviceForBuilds, $document);
|
||||
$this->deleteDeploymentFiles($deviceForSites, $document);
|
||||
$this->deleteDeploymentScreenshots($deviceForFiles, $dbForPlatform, $document);
|
||||
$this->deleteDeploymentRules($dbForPlatform, $document, $project, $certificates);
|
||||
@@ -796,12 +797,26 @@ class Deletes extends Action
|
||||
/**
|
||||
* Delete builds
|
||||
*/
|
||||
Console::info("Deleting builds for site " . $siteId);
|
||||
foreach ($deploymentInternalIds as $deploymentInternalId) {
|
||||
$this->deleteByGroup('builds', [
|
||||
Query::equal('deploymentInternalId', [$deploymentInternalId])
|
||||
], $dbForProject, function (Document $document) use ($deviceForBuilds) {
|
||||
$this->deleteBuildFiles($deviceForBuilds, $document);
|
||||
Console::info("Deleting builds for site " . $siteId);
|
||||
foreach ($deploymentInternalIds as $deploymentInternalId) {
|
||||
$this->deleteByGroup('builds', [
|
||||
Query::equal('deploymentInternalId', [$deploymentInternalId])
|
||||
], $dbForProject, function (Document $document) use ($deviceForBuilds) {
|
||||
$this->deleteBuildFiles($deviceForBuilds, $document);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete rules for all deployments of the site
|
||||
*/
|
||||
foreach ($deploymentIds as $deploymentId) {
|
||||
Console::info("Deleting rules for site " . $siteId . "'s deployment " . $deploymentId);
|
||||
$this->deleteByGroup('rules', [
|
||||
Query::equal('type', ['deployment']),
|
||||
Query::equal('value', [$deploymentId]),
|
||||
Query::equal('projectInternalId', [$project->getInternalId()])
|
||||
], $dbForPlatform, function (Document $document) use ($dbForPlatform, $certificates) {
|
||||
$this->deleteRule($dbForPlatform, $document, $certificates);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -869,25 +884,13 @@ class Deletes extends Action
|
||||
$deploymentInternalIds = [];
|
||||
$this->deleteByGroup('deployments', [
|
||||
Query::equal('resourceInternalId', [$functionInternalId])
|
||||
], $dbForProject, function (Document $document) use ($dbForPlatform, $project, $certificates, $deviceForFunctions, &$deploymentInternalIds) {
|
||||
], $dbForProject, function (Document $document) use ($dbForPlatform, $project, $certificates, $deviceForFunctions, $deviceForBuilds, &$deploymentInternalIds) {
|
||||
$deploymentInternalIds[] = $document->getInternalId();
|
||||
$this->deleteDeploymentFiles($deviceForFunctions, $document);
|
||||
$this->deleteBuildFiles($deviceForBuilds, $document);
|
||||
$this->deleteDeploymentRules($dbForPlatform, $document, $project, $certificates);
|
||||
});
|
||||
|
||||
/**
|
||||
* Delete builds
|
||||
*/
|
||||
Console::info("Deleting builds for function " . $functionId);
|
||||
|
||||
foreach ($deploymentInternalIds as $deploymentInternalId) {
|
||||
$this->deleteByGroup('builds', [
|
||||
Query::equal('deploymentInternalId', [$deploymentInternalId])
|
||||
], $dbForProject, function (Document $document) use ($deviceForBuilds) {
|
||||
$this->deleteBuildFiles($deviceForBuilds, $document);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Executions
|
||||
*/
|
||||
@@ -987,7 +990,7 @@ class Deletes extends Action
|
||||
private function deleteDeploymentFiles(Device $device, Document $deployment): void
|
||||
{
|
||||
$deploymentId = $deployment->getId();
|
||||
$deploymentPath = $deployment->getAttribute('path', '');
|
||||
$deploymentPath = $deployment->getAttribute('sourcePath', '');
|
||||
|
||||
if (empty($deploymentPath)) {
|
||||
Console::info("No deployment files for deployment " . $deploymentId);
|
||||
@@ -1016,13 +1019,13 @@ class Deletes extends Action
|
||||
* @param Document $build
|
||||
* @return void
|
||||
*/
|
||||
private function deleteBuildFiles(Device $device, Document $build): void
|
||||
private function deleteBuildFiles(Device $device, Document $deployment): void
|
||||
{
|
||||
$buildId = $build->getId();
|
||||
$buildPath = $build->getAttribute('path', '');
|
||||
$deploymentId = $deployment->getId();
|
||||
$buildPath = $deployment->getAttribute('buildPath', '');
|
||||
|
||||
if (empty($buildPath)) {
|
||||
Console::info("No build files for build " . $buildId);
|
||||
Console::info("No build files for deployment " . $deploymentId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1073,15 +1076,9 @@ class Deletes extends Action
|
||||
$this->deleteDeploymentScreenshots($deviceForFiles, $dbForPlatform, $document);
|
||||
|
||||
/**
|
||||
* Delete builds
|
||||
* Delete deployment build
|
||||
*/
|
||||
Console::info("Deleting builds for deployment " . $deploymentId);
|
||||
|
||||
$this->deleteByGroup('builds', [
|
||||
Query::equal('deploymentInternalId', [$deploymentInternalId])
|
||||
], $dbForProject, function (Document $document) use ($deviceForBuilds) {
|
||||
$this->deleteBuildFiles($deviceForBuilds, $document);
|
||||
});
|
||||
$this->deleteBuildFiles($deviceForBuilds, $document);
|
||||
|
||||
/**
|
||||
* Delete rules associated with the deployment
|
||||
|
||||
@@ -327,7 +327,7 @@ class Functions extends Action
|
||||
$user ??= new Document();
|
||||
$functionId = $function->getId();
|
||||
$deploymentId = $function->getAttribute('deployment', '');
|
||||
$spec = Config::getParam('runtime-specifications')[$function->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
$spec = Config::getParam('specifications')[$function->getAttribute('specification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
|
||||
$log->addTag('deploymentId', $deploymentId);
|
||||
|
||||
@@ -346,19 +346,7 @@ class Functions extends Action
|
||||
return;
|
||||
}
|
||||
|
||||
$buildId = $deployment->getAttribute('buildId', '');
|
||||
|
||||
$log->addTag('buildId', $buildId);
|
||||
|
||||
/** Check if build has exists */
|
||||
$build = $dbForProject->getDocument('builds', $buildId);
|
||||
if ($build->isEmpty()) {
|
||||
$errorMessage = 'The execution could not be completed because a corresponding deployment was not found. A function deployment needs to be created before it can be executed. Please create a deployment for your function and try again.';
|
||||
$this->fail($errorMessage, $dbForProject, $function, $trigger, $path, $method, $user, $jwt, $event);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($build->getAttribute('status') !== 'ready') {
|
||||
if ($deployment->getAttribute('status') !== 'ready') {
|
||||
$errorMessage = 'The execution could not be completed because the build is not ready. Please wait for the build to complete and try again.';
|
||||
$this->fail($errorMessage, $dbForProject, $function, $trigger, $path, $method, $user, $jwt, $event);
|
||||
return;
|
||||
@@ -513,7 +501,7 @@ class Functions extends Action
|
||||
variables: $vars,
|
||||
timeout: $function->getAttribute('timeout', 0),
|
||||
image: $runtime['image'],
|
||||
source: $build->getAttribute('path', ''),
|
||||
source: $deployment->getAttribute('buildPath', ''),
|
||||
entrypoint: $deployment->getAttribute('entrypoint', ''),
|
||||
version: $version,
|
||||
path: $path,
|
||||
|
||||
@@ -268,15 +268,13 @@ class StatsResources extends Action
|
||||
protected function countForFunctions(Database $dbForProject, Database $dbForLogs, string $region)
|
||||
{
|
||||
$deploymentsStorage = $dbForProject->sum('deployments', 'size');
|
||||
$buildsStorage = $dbForProject->sum('builds', 'size');
|
||||
$buildsStorage = $dbForProject->sum('deployments', 'buildSize');
|
||||
$this->createStatsDocuments($region, METRIC_DEPLOYMENTS_STORAGE, $deploymentsStorage);
|
||||
$this->createStatsDocuments($region, METRIC_BUILDS_STORAGE, $buildsStorage);
|
||||
|
||||
$deployments = $dbForProject->count('deployments');
|
||||
$builds = $dbForProject->count('builds');
|
||||
$this->createStatsDocuments($region, METRIC_DEPLOYMENTS, $deployments);
|
||||
$this->createStatsDocuments($region, METRIC_BUILDS, $builds);
|
||||
|
||||
$this->createStatsDocuments($region, METRIC_BUILDS, $deployments);
|
||||
|
||||
$this->foreachDocument($dbForProject, 'functions', [], function (Document $function) use ($dbForProject, $dbForLogs, $region) {
|
||||
$functionDeploymentsStorage = $dbForProject->sum('deployments', 'size', [
|
||||
@@ -302,9 +300,8 @@ class StatsResources extends Action
|
||||
$this->foreachDocument($dbForProject, 'deployments', [
|
||||
Query::equal('resourceInternalId', [$function->getInternalId()]),
|
||||
Query::equal('resourceType', [RESOURCE_TYPE_FUNCTIONS]),
|
||||
], function (Document $deployment) use ($dbForProject, &$functionBuildsStorage): void {
|
||||
$build = $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', ''));
|
||||
$functionBuildsStorage += $build->getAttribute('size', 0);
|
||||
], function (Document $deployment) use (&$functionBuildsStorage): void {
|
||||
$functionBuildsStorage += $deployment->getAttribute('buildSize', 0);
|
||||
});
|
||||
|
||||
$this->createStatsDocuments($region, str_replace(['{resourceType}','{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS,$function->getInternalId()], METRIC_RESOURCE_TYPE_ID_BUILDS_STORAGE), $functionBuildsStorage);
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Sites\Validator;
|
||||
|
||||
use Utopia\Validator;
|
||||
|
||||
class FrameworkSpecification extends Validator
|
||||
{
|
||||
private array $plan;
|
||||
|
||||
private array $specifications;
|
||||
|
||||
private float $maxCpus;
|
||||
|
||||
private int $maxMemory;
|
||||
|
||||
public function __construct(array $plan, array $specifications, float $maxCpus, int $maxMemory)
|
||||
{
|
||||
$this->plan = $plan;
|
||||
$this->specifications = $specifications;
|
||||
$this->maxCpus = $maxCpus;
|
||||
$this->maxMemory = $maxMemory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Allowed Specifications.
|
||||
*
|
||||
* Get allowed specifications taking into account the limits set by the environment variables and the plan.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllowedSpecifications(): array
|
||||
{
|
||||
$allowedSpecifications = [];
|
||||
|
||||
foreach ($this->specifications as $size => $values) {
|
||||
if ($values['cpus'] <= $this->maxCpus && $values['memory'] <= $this->maxMemory) {
|
||||
if (!empty($this->plan) && array_key_exists('frameworkSpecifications', $this->plan)) {
|
||||
if (!\in_array($size, $this->plan['frameworkSpecifications'])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$allowedSpecifications[] = $size;
|
||||
}
|
||||
}
|
||||
|
||||
return $allowedSpecifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Description.
|
||||
*
|
||||
* Returns validator description.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Specification must be one of: ' . implode(', ', $this->getAllowedSpecifications());
|
||||
}
|
||||
|
||||
/**
|
||||
* Is valid.
|
||||
*
|
||||
* Returns true if valid or false if not.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (empty($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!\is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!\in_array($value, $this->getAllowedSpecifications())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array.
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type.
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_STRING;
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,12 @@ namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
class Deployments extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
'size',
|
||||
'buildId',
|
||||
'buildSize',
|
||||
'sourceSize',
|
||||
'buildDuration',
|
||||
'status',
|
||||
'activate',
|
||||
'entrypoint',
|
||||
'commands',
|
||||
'type',
|
||||
'size'
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,7 +30,6 @@ use Appwrite\Utopia\Response\Model\AuthProvider;
|
||||
use Appwrite\Utopia\Response\Model\BaseList;
|
||||
use Appwrite\Utopia\Response\Model\Branch;
|
||||
use Appwrite\Utopia\Response\Model\Bucket;
|
||||
use Appwrite\Utopia\Response\Model\Build;
|
||||
use Appwrite\Utopia\Response\Model\Collection;
|
||||
use Appwrite\Utopia\Response\Model\ConsoleVariables;
|
||||
use Appwrite\Utopia\Response\Model\Continent;
|
||||
@@ -38,7 +37,8 @@ use Appwrite\Utopia\Response\Model\Country;
|
||||
use Appwrite\Utopia\Response\Model\Currency;
|
||||
use Appwrite\Utopia\Response\Model\Database;
|
||||
use Appwrite\Utopia\Response\Model\Deployment;
|
||||
use Appwrite\Utopia\Response\Model\Detection;
|
||||
use Appwrite\Utopia\Response\Model\DetectionFramework;
|
||||
use Appwrite\Utopia\Response\Model\DetectionRuntime;
|
||||
use Appwrite\Utopia\Response\Model\Document as ModelDocument;
|
||||
use Appwrite\Utopia\Response\Model\Error;
|
||||
use Appwrite\Utopia\Response\Model\ErrorDev;
|
||||
@@ -83,6 +83,8 @@ use Appwrite\Utopia\Response\Model\Preferences;
|
||||
use Appwrite\Utopia\Response\Model\Project;
|
||||
use Appwrite\Utopia\Response\Model\Provider;
|
||||
use Appwrite\Utopia\Response\Model\ProviderRepository;
|
||||
use Appwrite\Utopia\Response\Model\ProviderRepositoryFramework;
|
||||
use Appwrite\Utopia\Response\Model\ProviderRepositoryRuntime;
|
||||
use Appwrite\Utopia\Response\Model\Rule;
|
||||
use Appwrite\Utopia\Response\Model\Runtime;
|
||||
use Appwrite\Utopia\Response\Model\Session;
|
||||
@@ -247,9 +249,14 @@ class Response extends SwooleResponse
|
||||
public const MODEL_INSTALLATION_LIST = 'installationList';
|
||||
public const MODEL_PROVIDER_REPOSITORY = 'providerRepository';
|
||||
public const MODEL_PROVIDER_REPOSITORY_LIST = 'providerRepositoryList';
|
||||
public const MODEL_PROVIDER_REPOSITORY_FRAMEWORK = 'providerRepositoryFramework';
|
||||
public const MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST = 'providerRepositoryFrameworkList';
|
||||
public const MODEL_PROVIDER_REPOSITORY_RUNTIME = 'providerRepositoryRuntime';
|
||||
public const MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST = 'providerRepositoryRuntimeList';
|
||||
public const MODEL_BRANCH = 'branch';
|
||||
public const MODEL_BRANCH_LIST = 'branchList';
|
||||
public const MODEL_DETECTION = 'detection';
|
||||
public const MODEL_DETECTION_FRAMEWORK = 'detectionFramework';
|
||||
public const MODEL_DETECTION_RUNTIME = 'detectionRuntime';
|
||||
public const MODEL_VCS_CONTENT = 'vcsContent';
|
||||
public const MODEL_VCS_CONTENT_LIST = 'vcsContentList';
|
||||
|
||||
@@ -272,8 +279,6 @@ class Response extends SwooleResponse
|
||||
public const MODEL_DEPLOYMENT_LIST = 'deploymentList';
|
||||
public const MODEL_EXECUTION = 'execution';
|
||||
public const MODEL_EXECUTION_LIST = 'executionList';
|
||||
public const MODEL_BUILD = 'build';
|
||||
public const MODEL_BUILD_LIST = 'buildList'; // Not used anywhere yet
|
||||
public const MODEL_FUNC_PERMISSIONS = 'funcPermissions';
|
||||
public const MODEL_HEADERS = 'headers';
|
||||
public const MODEL_SPECIFICATION = 'specification';
|
||||
@@ -375,13 +380,13 @@ class Response extends SwooleResponse
|
||||
->setModel(new BaseList('Functions List', self::MODEL_FUNCTION_LIST, 'functions', self::MODEL_FUNCTION))
|
||||
->setModel(new BaseList('Function Templates List', self::MODEL_TEMPLATE_FUNCTION_LIST, 'templates', self::MODEL_TEMPLATE_FUNCTION))
|
||||
->setModel(new BaseList('Installations List', self::MODEL_INSTALLATION_LIST, 'installations', self::MODEL_INSTALLATION))
|
||||
->setModel(new BaseList('Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_LIST, 'providerRepositories', self::MODEL_PROVIDER_REPOSITORY))
|
||||
->setModel(new BaseList('Framework Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST, 'frameworkProviderRepositories', self::MODEL_PROVIDER_REPOSITORY_FRAMEWORK))
|
||||
->setModel(new BaseList('Runtime Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST, 'runtimeProviderRepositories', self::MODEL_PROVIDER_REPOSITORY_RUNTIME))
|
||||
->setModel(new BaseList('Branches List', self::MODEL_BRANCH_LIST, 'branches', self::MODEL_BRANCH))
|
||||
->setModel(new BaseList('Frameworks List', self::MODEL_FRAMEWORK_LIST, 'frameworks', self::MODEL_FRAMEWORK))
|
||||
->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))
|
||||
->setModel(new BaseList('Builds List', self::MODEL_BUILD_LIST, 'builds', self::MODEL_BUILD)) // Not used anywhere yet
|
||||
->setModel(new BaseList('Projects List', self::MODEL_PROJECT_LIST, 'projects', self::MODEL_PROJECT, true, false))
|
||||
->setModel(new BaseList('Webhooks List', self::MODEL_WEBHOOK_LIST, 'webhooks', self::MODEL_WEBHOOK, true, false))
|
||||
->setModel(new BaseList('API Keys List', self::MODEL_KEY_LIST, 'keys', self::MODEL_KEY, true, false))
|
||||
@@ -453,7 +458,10 @@ class Response extends SwooleResponse
|
||||
->setModel(new TemplateVariable())
|
||||
->setModel(new Installation())
|
||||
->setModel(new ProviderRepository())
|
||||
->setModel(new Detection())
|
||||
->setModel(new ProviderRepositoryFramework())
|
||||
->setModel(new ProviderRepositoryRuntime())
|
||||
->setModel(new DetectionFramework())
|
||||
->setModel(new DetectionRuntime())
|
||||
->setModel(new VcsContent())
|
||||
->setModel(new Branch())
|
||||
->setModel(new Runtime())
|
||||
@@ -461,7 +469,6 @@ class Response extends SwooleResponse
|
||||
->setModel(new FrameworkAdapter())
|
||||
->setModel(new Deployment())
|
||||
->setModel(new Execution())
|
||||
->setModel(new Build())
|
||||
->setModel(new Project())
|
||||
->setModel(new Webhook())
|
||||
->setModel(new Key())
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class Build extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('$id', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Build ID.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('deploymentId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The deployment that created this build.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
// Build Status
|
||||
// Failed - The deployment build has failed. More details can usually be found in buildStderr
|
||||
// Ready - The deployment build was successful and the deployment is ready to be deployed
|
||||
// Processing - The deployment is currently waiting to have a build triggered
|
||||
// Building - The deployment is currently being built
|
||||
->addRule('status', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The build status. There are a few different types and each one means something different. \nFailed - The deployment build has failed. More details can usually be found in buildStderr\nReady - The deployment build was successful and the deployment is ready to be deployed\nProcessing - The deployment is currently waiting to have a build triggered\nBuilding - The deployment is currently being built',
|
||||
'default' => '',
|
||||
'example' => 'ready',
|
||||
])
|
||||
->addRule('stdout', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The stdout of the build.',
|
||||
'default' => '',
|
||||
'example' => '',
|
||||
])
|
||||
->addRule('stderr', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'The stderr of the build.',
|
||||
'default' => '',
|
||||
'example' => '',
|
||||
])
|
||||
->addRule('startTime', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'The deployment creation date in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('endTime', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'The time the build was finished in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('duration', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'The build duration in seconds.',
|
||||
'default' => 0,
|
||||
'example' => 0,
|
||||
])
|
||||
->addRule('size', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'The code size in bytes.',
|
||||
'default' => 0,
|
||||
'example' => 128,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Build';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_BUILD;
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ class Deployment extends Model
|
||||
'default' => '',
|
||||
'example' => 'index.js',
|
||||
])
|
||||
->addRule('size', [
|
||||
->addRule('sourceSize', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'The code size in bytes.',
|
||||
'default' => 0,
|
||||
@@ -100,7 +100,7 @@ class Deployment extends Model
|
||||
'default' => '',
|
||||
'example' => 'Compiling source files...',
|
||||
])
|
||||
->addRule('buildTime', [
|
||||
->addRule('buildDuration', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'The current build time in seconds.',
|
||||
'default' => 0,
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class DetectionFramework extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('framework', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Framework',
|
||||
'default' => '',
|
||||
'example' => 'nuxt',
|
||||
])
|
||||
->addRule('installCommand', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Site Install Command',
|
||||
'default' => '',
|
||||
'example' => 'npm install',
|
||||
])
|
||||
->addRule('buildCommand', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Site Build Command',
|
||||
'default' => '',
|
||||
'example' => 'npm run build',
|
||||
])
|
||||
->addRule('outputDirectory', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Site Output Directory',
|
||||
'default' => '',
|
||||
'example' => 'dist',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'DetectionFramework';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_DETECTION_FRAMEWORK;
|
||||
}
|
||||
}
|
||||
+15
-3
@@ -5,7 +5,7 @@ namespace Appwrite\Utopia\Response\Model;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class Detection extends Model
|
||||
class DetectionRuntime extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
@@ -15,6 +15,18 @@ class Detection extends Model
|
||||
'description' => 'Runtime',
|
||||
'default' => '',
|
||||
'example' => 'node',
|
||||
])
|
||||
->addRule('entrypoint', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Function Entrypoint',
|
||||
'default' => '',
|
||||
'example' => 'index.js',
|
||||
])
|
||||
->addRule('commands', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Function install and build commands',
|
||||
'default' => '',
|
||||
'example' => 'npm install && npm run build',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -25,7 +37,7 @@ class Detection extends Model
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Detection';
|
||||
return 'DetectionRuntime';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,6 +47,6 @@ class Detection extends Model
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_DETECTION;
|
||||
return Response::MODEL_DETECTION_RUNTIME;
|
||||
}
|
||||
}
|
||||
@@ -41,12 +41,6 @@ class ProviderRepository extends Model
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('runtime', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Auto-detected runtime suggestion. Empty if getting response of getRuntime().',
|
||||
'default' => '',
|
||||
'example' => 'node',
|
||||
])
|
||||
->addRule('pushedAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Last commit date in ISO 8601 format.',
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
|
||||
class ProviderRepositoryFramework extends ProviderRepository
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->addRule('framework', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Auto-detected framework. Empty if type is not "framework".',
|
||||
'default' => '',
|
||||
'example' => 'nextjs',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'ProviderRepositoryFramework';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
|
||||
class ProviderRepositoryRuntime extends ProviderRepository
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->addRule('runtime', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Auto-detected runtime. Empty if type is not "runtime".',
|
||||
'default' => '',
|
||||
'example' => 'node-22',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'ProviderRepositoryRuntime';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_PROVIDER_REPOSITORY_RUNTIME;
|
||||
}
|
||||
}
|
||||
@@ -135,9 +135,9 @@ class Executor
|
||||
* @param string $projectId
|
||||
* @param string $deploymentId
|
||||
*/
|
||||
public function deleteRuntime(string $projectId, string $deploymentId)
|
||||
public function deleteRuntime(string $projectId, string $deploymentId, string $suffix = '')
|
||||
{
|
||||
$runtimeId = "$projectId-$deploymentId";
|
||||
$runtimeId = "$projectId-$deploymentId" . $suffix;
|
||||
$route = "/runtimes/$runtimeId";
|
||||
|
||||
$response = $this->call(self::METHOD_DELETE, $route, [
|
||||
@@ -249,6 +249,31 @@ class Executor
|
||||
return $response['body'];
|
||||
}
|
||||
|
||||
public function createCommand(
|
||||
string $deploymentId,
|
||||
string $projectId,
|
||||
string $command,
|
||||
int $timeout
|
||||
) {
|
||||
$runtimeId = "$projectId-$deploymentId-build";
|
||||
$route = "/runtimes/$runtimeId/commands";
|
||||
|
||||
$params = [
|
||||
'command' => $command,
|
||||
'timeout' => $timeout
|
||||
];
|
||||
|
||||
$response = $this->call(self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout);
|
||||
|
||||
$status = $response['headers']['status-code'];
|
||||
if ($status >= 400) {
|
||||
$message = \is_string($response['body']) ? $response['body'] : $response['body']['message'];
|
||||
throw new \Exception($message, $status);
|
||||
}
|
||||
|
||||
return $response['body'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Call
|
||||
*
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Tests\E2E\General;
|
||||
|
||||
use Appwrite\Functions\Specification;
|
||||
use Appwrite\Platform\Modules\Compute\Specification;
|
||||
use Appwrite\Tests\Retry;
|
||||
use CURLFile;
|
||||
use DateTime;
|
||||
|
||||
@@ -390,4 +390,13 @@ trait FunctionsBase
|
||||
|
||||
return $deployment;
|
||||
}
|
||||
|
||||
protected function listSpecifications(): mixed
|
||||
{
|
||||
$specifications = $this->client->call(Client::METHOD_GET, '/functions/specifications', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
return $specifications;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Tests\E2E\Services\Functions;
|
||||
|
||||
use Appwrite\Functions\Specification;
|
||||
use Appwrite\Platform\Modules\Compute\Specification;
|
||||
use Appwrite\Tests\Retry;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
@@ -22,6 +22,41 @@ class FunctionsCustomServerTest extends Scope
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
|
||||
public function testListSpecs(): void
|
||||
{
|
||||
$specifications = $this->listSpecifications();
|
||||
$this->assertEquals(200, $specifications['headers']['status-code']);
|
||||
$this->assertGreaterThan(0, $specifications['body']['total']);
|
||||
$this->assertArrayHasKey(0, $specifications['body']['specifications']);
|
||||
$this->assertArrayHasKey('memory', $specifications['body']['specifications'][0]);
|
||||
$this->assertArrayHasKey('cpus', $specifications['body']['specifications'][0]);
|
||||
$this->assertArrayHasKey('enabled', $specifications['body']['specifications'][0]);
|
||||
$this->assertArrayHasKey('slug', $specifications['body']['specifications'][0]);
|
||||
|
||||
$function = $this->createFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Specs function',
|
||||
'runtime' => 'php-8.0',
|
||||
'specification' => $specifications['body']['specifications'][0]['slug']
|
||||
]);
|
||||
$this->assertEquals(201, $function['headers']['status-code']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $function['body']['specification']);
|
||||
|
||||
$function = $this->getFunction($function['body']['$id']);
|
||||
$this->assertEquals(200, $function['headers']['status-code']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $function['body']['specification']);
|
||||
|
||||
$this->cleanupFunction($function['body']['$id']);
|
||||
|
||||
$function = $this->createFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Specs function',
|
||||
'runtime' => 'php-8.0',
|
||||
'specification' => 'cheap-please'
|
||||
]);
|
||||
$this->assertEquals(400, $function['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testCreateFunction(): array
|
||||
{
|
||||
/**
|
||||
@@ -382,7 +417,7 @@ class FunctionsCustomServerTest extends Scope
|
||||
$lastDeployment = $deployments['body']['deployments'][0];
|
||||
|
||||
$this->assertNotEmpty($lastDeployment['$id']);
|
||||
$this->assertEquals(0, $lastDeployment['size']);
|
||||
$this->assertEquals(0, $lastDeployment['sourceSize']);
|
||||
|
||||
$deploymentId = $lastDeployment['$id'];
|
||||
|
||||
@@ -598,10 +633,10 @@ class FunctionsCustomServerTest extends Scope
|
||||
$this->assertNotEmpty($largeTag['body']['$id']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($largeTag['body']['$createdAt']));
|
||||
$this->assertEquals('index.php', $largeTag['body']['entrypoint']);
|
||||
$this->assertGreaterThan(1024 * 1024 * 5, $largeTag['body']['size']); // ~7MB video file
|
||||
$this->assertLessThan(1024 * 1024 * 10, $largeTag['body']['size']); // ~7MB video file
|
||||
$this->assertGreaterThan(1024 * 1024 * 5, $largeTag['body']['sourceSize']); // ~7MB video file
|
||||
$this->assertLessThan(1024 * 1024 * 10, $largeTag['body']['sourceSize']); // ~7MB video file
|
||||
|
||||
$deploymentSize = $largeTag['body']['size'];
|
||||
$deploymentSize = $largeTag['body']['sourceSize'];
|
||||
$deploymentId = $largeTag['body']['$id'];
|
||||
|
||||
$this->assertEventually(function () use ($functionId, $deploymentId, $deploymentSize) {
|
||||
@@ -609,7 +644,7 @@ class FunctionsCustomServerTest extends Scope
|
||||
|
||||
$this->assertEquals(200, $deployment['headers']['status-code']);
|
||||
$this->assertEquals('ready', $deployment['body']['status']);
|
||||
$this->assertEquals($deploymentSize, $deployment['body']['size']);
|
||||
$this->assertEquals($deploymentSize, $deployment['body']['sourceSize']);
|
||||
$this->assertGreaterThan(1024 * 1024 * 10, $deployment['body']['buildSize']); // ~7MB video file + 10MB sample file
|
||||
}, 500000, 1000);
|
||||
|
||||
@@ -655,7 +690,7 @@ class FunctionsCustomServerTest extends Scope
|
||||
$this->assertEquals($deployments['body']['total'], 3);
|
||||
$this->assertIsArray($deployments['body']['deployments']);
|
||||
$this->assertCount(3, $deployments['body']['deployments']);
|
||||
$this->assertArrayHasKey('size', $deployments['body']['deployments'][0]);
|
||||
$this->assertArrayHasKey('sourceSize', $deployments['body']['deployments'][0]);
|
||||
$this->assertArrayHasKey('buildSize', $deployments['body']['deployments'][0]);
|
||||
|
||||
$deployments = $this->listDeployments($functionId, [
|
||||
@@ -676,24 +711,6 @@ class FunctionsCustomServerTest extends Scope
|
||||
$this->assertEquals($deployments['headers']['status-code'], 200);
|
||||
$this->assertCount(2, $deployments['body']['deployments']);
|
||||
|
||||
$deployments = $this->listDeployments($functionId, [
|
||||
'queries' => [
|
||||
Query::equal('entrypoint', ['index.php'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals($deployments['headers']['status-code'], 200);
|
||||
$this->assertCount(3, $deployments['body']['deployments']);
|
||||
|
||||
$deployments = $this->listDeployments($functionId, [
|
||||
'queries' => [
|
||||
Query::equal('entrypoint', ['index.js'])->toString(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertEquals($deployments['headers']['status-code'], 200);
|
||||
$this->assertCount(0, $deployments['body']['deployments']);
|
||||
|
||||
$deployments = $this->listDeployments($functionId, [
|
||||
'search' => 'php-8.0'
|
||||
]);
|
||||
@@ -744,7 +761,7 @@ class FunctionsCustomServerTest extends Scope
|
||||
$functionId,
|
||||
[
|
||||
'queries' => [
|
||||
Query::greaterThan('size', 10000)->toString(),
|
||||
Query::greaterThan('sourceSize', 10000)->toString(),
|
||||
],
|
||||
]
|
||||
);
|
||||
@@ -756,7 +773,7 @@ class FunctionsCustomServerTest extends Scope
|
||||
$functionId,
|
||||
[
|
||||
'queries' => [
|
||||
Query::greaterThan('size', 0)->toString(),
|
||||
Query::greaterThan('sourceSize', 0)->toString(),
|
||||
],
|
||||
]
|
||||
);
|
||||
@@ -768,7 +785,7 @@ class FunctionsCustomServerTest extends Scope
|
||||
$functionId,
|
||||
[
|
||||
'queries' => [
|
||||
Query::greaterThan('size', -100)->toString(),
|
||||
Query::greaterThan('sourceSize', -100)->toString(),
|
||||
],
|
||||
]
|
||||
);
|
||||
@@ -789,16 +806,16 @@ class FunctionsCustomServerTest extends Scope
|
||||
$this->assertEquals(200, $deployments['headers']['status-code']);
|
||||
$this->assertGreaterThanOrEqual(1, $deployments['body']['total']);
|
||||
$this->assertNotEmpty($deployments['body']['deployments'][0]['$id']);
|
||||
$this->assertNotEmpty($deployments['body']['deployments'][0]['size']);
|
||||
$this->assertNotEmpty($deployments['body']['deployments'][0]['sourceSize']);
|
||||
|
||||
$deploymentId = $deployments['body']['deployments'][0]['$id'];
|
||||
$deploymentSize = $deployments['body']['deployments'][0]['size'];
|
||||
$deploymentSize = $deployments['body']['deployments'][0]['sourceSize'];
|
||||
|
||||
$deployments = $this->listDeployments(
|
||||
$functionId,
|
||||
[
|
||||
'queries' => [
|
||||
Query::equal('size', [$deploymentSize])->toString(),
|
||||
Query::equal('sourceSize', [$deploymentSize])->toString(),
|
||||
],
|
||||
]
|
||||
);
|
||||
@@ -815,7 +832,7 @@ class FunctionsCustomServerTest extends Scope
|
||||
|
||||
if (!empty($matchingDeployment)) {
|
||||
$deployment = reset($matchingDeployment);
|
||||
$this->assertEquals($deploymentSize, $deployment['size']);
|
||||
$this->assertEquals($deploymentSize, $deployment['sourceSize']);
|
||||
}
|
||||
|
||||
return $data;
|
||||
@@ -832,10 +849,10 @@ class FunctionsCustomServerTest extends Scope
|
||||
$deployment = $this->getDeployment($data['functionId'], $data['deploymentId']);
|
||||
|
||||
$this->assertEquals(200, $deployment['headers']['status-code']);
|
||||
$this->assertGreaterThan(0, $deployment['body']['buildTime']);
|
||||
$this->assertGreaterThan(0, $deployment['body']['buildDuration']);
|
||||
$this->assertNotEmpty($deployment['body']['status']);
|
||||
$this->assertNotEmpty($deployment['body']['buildLogs']);
|
||||
$this->assertArrayHasKey('size', $deployment['body']);
|
||||
$this->assertArrayHasKey('sourceSize', $deployment['body']);
|
||||
$this->assertArrayHasKey('buildSize', $deployment['body']);
|
||||
|
||||
/**
|
||||
@@ -2006,14 +2023,14 @@ class FunctionsCustomServerTest extends Scope
|
||||
$this->assertEquals(201, $execution['headers']['status-code']);
|
||||
$this->assertStringContainsString('APPWRITE_FUNCTION_ID', $execution['body']['responseBody']);
|
||||
|
||||
$site = $this->updateFunction($functionId, [
|
||||
$function = $this->updateFunction($functionId, [
|
||||
'runtime' => 'node-18.0',
|
||||
'name' => 'Duplicate Deployment Test',
|
||||
'entrypoint' => 'index.js',
|
||||
'commands' => 'rm index.js && mv maintenance.js index.js'
|
||||
]);
|
||||
$this->assertEquals(200, $site['headers']['status-code']);
|
||||
$this->assertStringContainsString('maintenance.js', $site['body']['commands']);
|
||||
$this->assertEquals(200, $function['headers']['status-code']);
|
||||
$this->assertStringContainsString('maintenance.js', $function['body']['commands']);
|
||||
|
||||
$deploymentId2 = $this->setupDuplicateDeployment($functionId, $deploymentId1);
|
||||
$this->assertNotEmpty($deploymentId2);
|
||||
|
||||
@@ -1568,7 +1568,7 @@ trait Base
|
||||
_id
|
||||
buildId
|
||||
entrypoint
|
||||
size
|
||||
buildSize
|
||||
status
|
||||
buildLogs
|
||||
}
|
||||
|
||||
@@ -415,4 +415,13 @@ trait SitesBase
|
||||
|
||||
return $deployment;
|
||||
}
|
||||
|
||||
protected function listSpecifications(): mixed
|
||||
{
|
||||
$specifications = $this->client->call(Client::METHOD_GET, '/sites/specifications', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
return $specifications;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
namespace Tests\E2E\Services\Sites;
|
||||
|
||||
use Appwrite\Sites\Specification;
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Platform\Modules\Compute\Specification;
|
||||
use Appwrite\Tests\Retry;
|
||||
use Tests\E2E\Client;
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
@@ -20,6 +21,43 @@ class SitesCustomServerTest extends Scope
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
|
||||
public function testListSpecs(): void
|
||||
{
|
||||
$specifications = $this->listSpecifications();
|
||||
$this->assertEquals(200, $specifications['headers']['status-code']);
|
||||
$this->assertGreaterThan(0, $specifications['body']['total']);
|
||||
$this->assertArrayHasKey(0, $specifications['body']['specifications']);
|
||||
$this->assertArrayHasKey('memory', $specifications['body']['specifications'][0]);
|
||||
$this->assertArrayHasKey('cpus', $specifications['body']['specifications'][0]);
|
||||
$this->assertArrayHasKey('enabled', $specifications['body']['specifications'][0]);
|
||||
$this->assertArrayHasKey('slug', $specifications['body']['specifications'][0]);
|
||||
|
||||
$site = $this->createSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'framework' => 'other',
|
||||
'name' => 'Specs site',
|
||||
'siteId' => ID::unique(),
|
||||
'specification' => $specifications['body']['specifications'][0]['slug']
|
||||
]);
|
||||
$this->assertEquals(201, $site['headers']['status-code']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $site['body']['specification']);
|
||||
|
||||
$site = $this->getSite($site['body']['$id']);
|
||||
$this->assertEquals(200, $site['headers']['status-code']);
|
||||
$this->assertEquals($specifications['body']['specifications'][0]['slug'], $site['body']['specification']);
|
||||
|
||||
$this->cleanupSite($site['body']['$id']);
|
||||
|
||||
$site = $this->createSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'framework' => 'other',
|
||||
'name' => 'Specs site',
|
||||
'siteId' => ID::unique(),
|
||||
'specification' => 'cheap-please'
|
||||
]);
|
||||
$this->assertEquals(400, $site['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testCreateSite(): void
|
||||
{
|
||||
/**
|
||||
@@ -27,7 +65,7 @@ class SitesCustomServerTest extends Scope
|
||||
*/
|
||||
$site = $this->createSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
@@ -75,7 +113,7 @@ class SitesCustomServerTest extends Scope
|
||||
'framework' => 'other',
|
||||
'buildRuntime' => 'node-22',
|
||||
'outputDirectory' => './',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
]);
|
||||
|
||||
$this->assertNotEmpty($siteId);
|
||||
@@ -141,7 +179,7 @@ class SitesCustomServerTest extends Scope
|
||||
{
|
||||
$site = $this->createSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
@@ -325,6 +363,168 @@ class SitesCustomServerTest extends Scope
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testAdapterDetectionAstroSSR(): void
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Astro SSR site',
|
||||
'framework' => 'astro',
|
||||
'buildRuntime' => 'node-22',
|
||||
'outputDirectory' => './dist',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
]);
|
||||
$this->assertNotEmpty($siteId);
|
||||
|
||||
$site = $this->getSite($siteId);
|
||||
$this->assertEquals('200', $site['headers']['status-code']);
|
||||
$this->assertArrayHasKey('adapter', $site['body']);
|
||||
$this->assertEmpty($site['body']['adapter']);
|
||||
|
||||
$domain = $this->setupSiteDomain($siteId);
|
||||
$this->assertNotEmpty($domain);
|
||||
|
||||
$deploymentId = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('astro'),
|
||||
'activate' => 'true'
|
||||
]);
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$site = $this->getSite($siteId);
|
||||
$this->assertEquals('ssr', $site['body']['adapter']);
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testAdapterDetectionAstroStatic(): void
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Astro static site',
|
||||
'framework' => 'astro',
|
||||
'buildRuntime' => 'node-22',
|
||||
'outputDirectory' => './dist',
|
||||
'buildCommand' => 'npm run build',
|
||||
'installCommand' => 'npm install',
|
||||
]);
|
||||
$this->assertNotEmpty($siteId);
|
||||
|
||||
$site = $this->getSite($siteId);
|
||||
$this->assertEquals('200', $site['headers']['status-code']);
|
||||
$this->assertArrayHasKey('adapter', $site['body']);
|
||||
$this->assertEmpty($site['body']['adapter']);
|
||||
|
||||
$domain = $this->setupSiteDomain($siteId);
|
||||
$this->assertNotEmpty($domain);
|
||||
|
||||
$deploymentId = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('astro-static'),
|
||||
'activate' => 'true'
|
||||
]);
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$site = $this->getSite($siteId);
|
||||
$this->assertEquals('200', $site['headers']['status-code']);
|
||||
$this->assertEquals('static', $site['body']['adapter']);
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testAdapterDetectionStatic(): void
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Static site',
|
||||
'framework' => 'other',
|
||||
'buildRuntime' => 'node-22',
|
||||
'outputDirectory' => '',
|
||||
'buildCommand' => '',
|
||||
'installCommand' => '',
|
||||
]);
|
||||
$this->assertNotEmpty($siteId);
|
||||
|
||||
$site = $this->getSite($siteId);
|
||||
$this->assertEquals('200', $site['headers']['status-code']);
|
||||
$this->assertArrayHasKey('adapter', $site['body']);
|
||||
$this->assertEmpty($site['body']['adapter']);
|
||||
|
||||
$domain = $this->setupSiteDomain($siteId);
|
||||
$this->assertNotEmpty($domain);
|
||||
|
||||
$deploymentId = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('static'),
|
||||
'activate' => 'true'
|
||||
]);
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$site = $this->getSite($siteId);
|
||||
$this->assertEquals('200', $site['headers']['status-code']);
|
||||
$this->assertEquals('static', $site['body']['adapter']);
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testAdapterDetectionStaticSPA(): void
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'siteId' => ID::unique(),
|
||||
'name' => 'Static site',
|
||||
'framework' => 'other',
|
||||
'buildRuntime' => 'node-22',
|
||||
'outputDirectory' => '',
|
||||
'buildCommand' => '',
|
||||
'installCommand' => '',
|
||||
]);
|
||||
$this->assertNotEmpty($siteId);
|
||||
|
||||
$site = $this->getSite($siteId);
|
||||
$this->assertEquals('200', $site['headers']['status-code']);
|
||||
$this->assertArrayHasKey('adapter', $site['body']);
|
||||
$this->assertArrayHasKey('fallbackFile', $site['body']);
|
||||
$this->assertEmpty($site['body']['adapter']);
|
||||
$this->assertEmpty($site['body']['fallbackFile']);
|
||||
|
||||
$domain = $this->setupSiteDomain($siteId);
|
||||
$this->assertNotEmpty($domain);
|
||||
|
||||
$deploymentId = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('static-single-file'),
|
||||
'activate' => 'true'
|
||||
]);
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$site = $this->getSite($siteId);
|
||||
$this->assertEquals('200', $site['headers']['status-code']);
|
||||
$this->assertEquals('static', $site['body']['adapter']);
|
||||
$this->assertEquals('main.html', $site['body']['fallbackFile']);
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString('Main page', $response['body']);
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/something');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString('Main page', $response['body']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testListSites(): void
|
||||
{
|
||||
/**
|
||||
@@ -332,7 +532,7 @@ class SitesCustomServerTest extends Scope
|
||||
*/
|
||||
$siteId = $this->setupSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
@@ -412,7 +612,7 @@ class SitesCustomServerTest extends Scope
|
||||
*/
|
||||
$siteId2 = $this->setupSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site 2',
|
||||
'outputDirectory' => './',
|
||||
@@ -468,7 +668,7 @@ class SitesCustomServerTest extends Scope
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
@@ -501,7 +701,7 @@ class SitesCustomServerTest extends Scope
|
||||
{
|
||||
$site = $this->createSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
@@ -518,7 +718,7 @@ class SitesCustomServerTest extends Scope
|
||||
|
||||
$site = $this->updateSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site Updated',
|
||||
'outputDirectory' => './',
|
||||
@@ -598,7 +798,7 @@ class SitesCustomServerTest extends Scope
|
||||
$lastDeployment = $deployments['body']['deployments'][0];
|
||||
|
||||
$this->assertNotEmpty($lastDeployment['$id']);
|
||||
$this->assertEquals(0, $lastDeployment['size']);
|
||||
$this->assertEquals(0, $lastDeployment['sourceSize']);
|
||||
|
||||
$deploymentId = $lastDeployment['$id'];
|
||||
|
||||
@@ -619,7 +819,7 @@ class SitesCustomServerTest extends Scope
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
@@ -680,7 +880,7 @@ class SitesCustomServerTest extends Scope
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
@@ -732,7 +932,7 @@ class SitesCustomServerTest extends Scope
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
@@ -777,7 +977,7 @@ class SitesCustomServerTest extends Scope
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
@@ -812,7 +1012,7 @@ class SitesCustomServerTest extends Scope
|
||||
$this->assertEquals($deployments['body']['total'], 2);
|
||||
$this->assertIsArray($deployments['body']['deployments']);
|
||||
$this->assertCount(2, $deployments['body']['deployments']);
|
||||
$this->assertArrayHasKey('size', $deployments['body']['deployments'][0]);
|
||||
$this->assertArrayHasKey('sourceSize', $deployments['body']['deployments'][0]);
|
||||
$this->assertArrayHasKey('buildSize', $deployments['body']['deployments'][0]);
|
||||
|
||||
$deployments = $this->listDeployments($siteId, [
|
||||
@@ -873,7 +1073,7 @@ class SitesCustomServerTest extends Scope
|
||||
$siteId,
|
||||
[
|
||||
'queries' => [
|
||||
Query::greaterThan('size', 10000)->toString(),
|
||||
Query::greaterThan('sourceSize', 10000)->toString(),
|
||||
],
|
||||
]
|
||||
);
|
||||
@@ -885,7 +1085,7 @@ class SitesCustomServerTest extends Scope
|
||||
$siteId,
|
||||
[
|
||||
'queries' => [
|
||||
Query::greaterThan('size', 0)->toString(),
|
||||
Query::greaterThan('sourceSize', 0)->toString(),
|
||||
],
|
||||
]
|
||||
);
|
||||
@@ -897,7 +1097,7 @@ class SitesCustomServerTest extends Scope
|
||||
$siteId,
|
||||
[
|
||||
'queries' => [
|
||||
Query::greaterThan('size', -100)->toString(),
|
||||
Query::greaterThan('sourceSize', -100)->toString(),
|
||||
],
|
||||
]
|
||||
);
|
||||
@@ -918,16 +1118,16 @@ class SitesCustomServerTest extends Scope
|
||||
$this->assertEquals(200, $deployments['headers']['status-code']);
|
||||
$this->assertGreaterThanOrEqual(1, $deployments['body']['total']);
|
||||
$this->assertNotEmpty($deployments['body']['deployments'][0]['$id']);
|
||||
$this->assertNotEmpty($deployments['body']['deployments'][0]['size']);
|
||||
$this->assertNotEmpty($deployments['body']['deployments'][0]['sourceSize']);
|
||||
|
||||
$deploymentId = $deployments['body']['deployments'][0]['$id'];
|
||||
$deploymentSize = $deployments['body']['deployments'][0]['size'];
|
||||
$deploymentSize = $deployments['body']['deployments'][0]['sourceSize'];
|
||||
|
||||
$deployments = $this->listDeployments(
|
||||
$siteId,
|
||||
[
|
||||
'queries' => [
|
||||
Query::equal('size', [$deploymentSize])->toString(),
|
||||
Query::equal('sourceSize', [$deploymentSize])->toString(),
|
||||
],
|
||||
]
|
||||
);
|
||||
@@ -944,7 +1144,7 @@ class SitesCustomServerTest extends Scope
|
||||
|
||||
if (!empty($matchingDeployment)) {
|
||||
$deployment = reset($matchingDeployment);
|
||||
$this->assertEquals($deploymentSize, $deployment['size']);
|
||||
$this->assertEquals($deploymentSize, $deployment['sourceSize']);
|
||||
}
|
||||
|
||||
$this->cleanupDeployment($siteId, $deploymentIdActive);
|
||||
@@ -956,7 +1156,7 @@ class SitesCustomServerTest extends Scope
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
@@ -986,10 +1186,10 @@ class SitesCustomServerTest extends Scope
|
||||
$deployment = $this->getDeployment($siteId, $deploymentId);
|
||||
|
||||
$this->assertEquals(200, $deployment['headers']['status-code']);
|
||||
$this->assertGreaterThan(0, $deployment['body']['buildTime']);
|
||||
$this->assertGreaterThan(0, $deployment['body']['buildDuration']);
|
||||
$this->assertNotEmpty($deployment['body']['status']);
|
||||
$this->assertNotEmpty($deployment['body']['buildLogs']);
|
||||
$this->assertArrayHasKey('size', $deployment['body']);
|
||||
$this->assertArrayHasKey('sourceSize', $deployment['body']);
|
||||
$this->assertArrayHasKey('buildSize', $deployment['body']);
|
||||
|
||||
/**
|
||||
@@ -1007,7 +1207,7 @@ class SitesCustomServerTest extends Scope
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
@@ -1024,7 +1224,7 @@ class SitesCustomServerTest extends Scope
|
||||
// Change the function specs
|
||||
$site = $this->updateSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
@@ -1041,7 +1241,7 @@ class SitesCustomServerTest extends Scope
|
||||
// Change the specs to 1vcpu 512mb
|
||||
$site = $this->updateSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
@@ -1061,7 +1261,7 @@ class SitesCustomServerTest extends Scope
|
||||
|
||||
$site = $this->updateSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
@@ -1081,7 +1281,7 @@ class SitesCustomServerTest extends Scope
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
@@ -1125,7 +1325,7 @@ class SitesCustomServerTest extends Scope
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'outputDirectory' => './',
|
||||
@@ -1481,7 +1681,18 @@ class SitesCustomServerTest extends Scope
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $oldDeploymentDomain);
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/');
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/', followRedirects: false);
|
||||
$this->assertEquals(301, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString('/console/auth/preview', $response['headers']['location']);
|
||||
|
||||
$jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 0);
|
||||
$apiKey = $jwtObj->encode([
|
||||
'projectCheckDisabled' => true,
|
||||
'previewAuthDisabled' => true,
|
||||
]);
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/', followRedirects: false, headers: [
|
||||
'x-appwrite-key' => API_KEY_DYNAMIC . '_' . $apiKey,
|
||||
]);
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Hello Appwrite", $response['body']);
|
||||
$this->assertStringContainsString("Preview by", $response['body']);
|
||||
@@ -1629,7 +1840,7 @@ class SitesCustomServerTest extends Scope
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'fallbackFile' => null,
|
||||
'fallbackFile' => '',
|
||||
'framework' => 'other',
|
||||
'name' => 'Test Site',
|
||||
'adapter' => 'static',
|
||||
@@ -1837,4 +2048,165 @@ class SitesCustomServerTest extends Scope
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
|
||||
public function testPreviewDomain(): void
|
||||
{
|
||||
$siteId = $this->setupSite([
|
||||
'buildRuntime' => 'node-22',
|
||||
'framework' => 'other',
|
||||
'name' => 'Authorized preview site',
|
||||
'siteId' => ID::unique(),
|
||||
'adapter' => 'static',
|
||||
]);
|
||||
$this->assertNotEmpty($siteId);
|
||||
|
||||
$deploymentId = $this->setupDeployment($siteId, [
|
||||
'code' => $this->packageSite('static'),
|
||||
'activate' => true
|
||||
]);
|
||||
$this->assertNotEmpty($deploymentId);
|
||||
|
||||
$domain = $this->getDeploymentDomain($deploymentId);
|
||||
$this->assertNotEmpty($domain);
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/contact', followRedirects: false);
|
||||
$this->assertEquals(301, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString('/console/auth/preview', $response['headers']['location']);
|
||||
$this->assertStringContainsString('projectId=' . $this->getProject()['$id'], $response['headers']['location']);
|
||||
$this->assertStringContainsString('origin=', $response['headers']['location']);
|
||||
$this->assertStringContainsString('path=%2Fcontact', $response['headers']['location']);
|
||||
|
||||
$session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
]), [
|
||||
'email' => $this->getRoot()['email'],
|
||||
'password' => 'password'
|
||||
]);
|
||||
$this->assertEquals(201, $session['headers']['status-code']);
|
||||
$this->assertNotEmpty($session['cookies']['a_session_console']);
|
||||
$this->assertNotEmpty($session['body']['$id']);
|
||||
$cookie = 'a_session_console=' . $session['cookies']['a_session_console'];
|
||||
|
||||
$jwt = $this->client->call(Client::METHOD_POST, '/account/jwts', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'cookie' => $cookie,
|
||||
'x-appwrite-project' => 'console',
|
||||
]), []);
|
||||
$this->assertEquals(201, $jwt['headers']['status-code']);
|
||||
$this->assertNotEmpty($jwt['body']['jwt']);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/_appwrite/authorize', params: [
|
||||
'jwt' => $jwt['body']['jwt'],
|
||||
'path' => '/contact'
|
||||
], followRedirects: false);
|
||||
$this->assertEquals(301, $response['headers']['status-code']);
|
||||
$this->assertArrayHasKey('set-cookie', $response['headers']);
|
||||
$this->assertStringContainsString('a_jwt_console=', $response['headers']['set-cookie']);
|
||||
$this->assertStringContainsString('httponly', $response['headers']['set-cookie']);
|
||||
$this->assertStringContainsString('domain=' . $domain, $response['headers']['set-cookie']);
|
||||
$this->assertStringContainsString('path=/', $response['headers']['set-cookie']);
|
||||
$this->assertNotEmpty($response['cookies']['a_jwt_console']);
|
||||
$this->assertEquals($jwt['body']['jwt'], $response['cookies']['a_jwt_console']);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/contact', headers: [
|
||||
'cookie' => 'a_jwt_console=' . $jwt['body']['jwt']
|
||||
], followRedirects: false);
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Contact page", $response['body']);
|
||||
$this->assertStringContainsString("Preview by", $response['body']);
|
||||
|
||||
// Failure: Session missing (old bad, new ok)
|
||||
$session = $this->client->call(Client::METHOD_DELETE, '/account/sessions/current', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'cookie' => $cookie,
|
||||
'x-appwrite-project' => 'console',
|
||||
]), []);
|
||||
$this->assertEquals(204, $session['headers']['status-code']);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/contact', headers: [
|
||||
'cookie' => 'a_jwt_console=' . $jwt['body']['jwt']
|
||||
], followRedirects: false);
|
||||
$this->assertEquals(301, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString('/console/auth/preview', $response['headers']['location']);
|
||||
|
||||
// Failure: User missing
|
||||
$cookie = 'a_session_console=' .$this->getRoot()['session'];
|
||||
$jwt = $this->client->call(Client::METHOD_POST, '/account/jwts', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'cookie' => $cookie,
|
||||
'x-appwrite-project' => 'console',
|
||||
]), []);
|
||||
$this->assertEquals(201, $jwt['headers']['status-code']);
|
||||
$this->assertNotEmpty($jwt['body']['jwt']);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/contact', headers: [
|
||||
'cookie' => 'a_jwt_console=' . $jwt['body']['jwt']
|
||||
], followRedirects: false);
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString("Contact page", $response['body']);
|
||||
$this->assertStringContainsString("Preview by", $response['body']);
|
||||
|
||||
$user = $this->client->call(Client::METHOD_PATCH, '/account/status', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'cookie' => $cookie,
|
||||
'x-appwrite-project' => 'console',
|
||||
]), []);
|
||||
$this->assertEquals(200, $user['headers']['status-code']);
|
||||
$this->assertFalse($user['body']['status']);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/contact', headers: [
|
||||
'cookie' => 'a_jwt_console=' . $jwt['body']['jwt']
|
||||
], followRedirects: false);
|
||||
$this->assertEquals(301, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString('/console/auth/preview', $response['headers']['location']);
|
||||
|
||||
// Failure: Membership missing
|
||||
$user = $this->client->call(Client::METHOD_POST, '/account', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'userId' => ID::unique(),
|
||||
'email' => 'newuser@appwrite.io',
|
||||
'password' => 'password'
|
||||
]);
|
||||
$this->assertEquals(201, $user['headers']['status-code']);
|
||||
|
||||
$session = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
], [
|
||||
'email' => 'newuser@appwrite.io',
|
||||
'password' => 'password',
|
||||
]);
|
||||
$this->assertEquals(201, $session['headers']['status-code']);
|
||||
$this->assertNotEmpty($session['cookies']['a_session_console']);
|
||||
$cookie = 'a_session_console=' . $session['cookies']['a_session_console'];
|
||||
|
||||
$jwt = $this->client->call(Client::METHOD_POST, '/account/jwts', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'cookie' => $cookie,
|
||||
'x-appwrite-project' => 'console',
|
||||
]), []);
|
||||
$this->assertEquals(201, $jwt['headers']['status-code']);
|
||||
$this->assertNotEmpty($jwt['body']['jwt']);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/contact', headers: [
|
||||
'cookie' => 'a_jwt_console=' . $jwt['body']['jwt']
|
||||
], followRedirects: false);
|
||||
$this->assertEquals(301, $response['headers']['status-code']);
|
||||
$this->assertStringContainsString('/console/auth/preview', $response['headers']['location']);
|
||||
|
||||
$this->cleanupSite($siteId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ class VCSConsoleClientTest extends Scope
|
||||
public string $providerInstallationId = '42954928'; // appwrite-test
|
||||
public string $providerRepositoryId = '705764267'; // ruby-starter (public)
|
||||
public string $providerRepositoryId2 = '708688544'; // function1.4 (private)
|
||||
public string $providerRepositoryId3 = '943139433'; // svelte-starter (public)
|
||||
public string $providerRepositoryId4 = '943245292'; // templates-for-sites (public)
|
||||
|
||||
public function testGitHubAuthorize(): string
|
||||
{
|
||||
@@ -67,24 +69,102 @@ class VCSConsoleClientTest extends Scope
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$runtime = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/detection', array_merge([
|
||||
$runtime = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/detections', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
'content-type' => 'application/json',
|
||||
], $this->getHeaders()), [
|
||||
'providerRepositoryId' => $this->providerRepositoryId,
|
||||
'type' => 'runtime',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $runtime['headers']['status-code']);
|
||||
$this->assertEquals($runtime['body']['runtime'], 'ruby-3.1');
|
||||
$this->assertEquals($runtime['body']['runtime'], 'ruby-3.3');
|
||||
$this->assertEquals($runtime['body']['commands'], 'bundle install && bundle exec rake build');
|
||||
$this->assertEquals($runtime['body']['entrypoint'], 'main.rb');
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
$runtime = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/providerRepositories/randomRepositoryId/detection', array_merge([
|
||||
$runtime = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/detections', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
'content-type' => 'application/json',
|
||||
], $this->getHeaders()), [
|
||||
'providerRepositoryId' => 'randomRepositoryId', // Invalid repository ID
|
||||
'type' => 'runtime',
|
||||
]);
|
||||
|
||||
$this->assertEquals(404, $runtime['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGitHubAuthorize
|
||||
*/
|
||||
public function testDetectFramework(string $installationId)
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$framework = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/detections', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'content-type' => 'application/json',
|
||||
], $this->getHeaders()), [
|
||||
'providerRepositoryId' => $this->providerRepositoryId3,
|
||||
'type' => 'framework',
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $framework['headers']['status-code']);
|
||||
$this->assertEquals($framework['body']['framework'], 'sveltekit');
|
||||
$this->assertEquals($framework['body']['installCommand'], 'npm install');
|
||||
$this->assertEquals($framework['body']['buildCommand'], 'npm run build');
|
||||
$this->assertEquals($framework['body']['outputDirectory'], './build');
|
||||
|
||||
$framework = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/detections', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'content-type' => 'application/json',
|
||||
], $this->getHeaders()), [
|
||||
'providerRepositoryId' => $this->providerRepositoryId4,
|
||||
'type' => 'framework',
|
||||
'providerRootDirectory' => 'astro/starter'
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $framework['headers']['status-code']);
|
||||
$this->assertEquals($framework['body']['framework'], 'astro');
|
||||
$this->assertEquals($framework['body']['installCommand'], 'npm install');
|
||||
$this->assertEquals($framework['body']['buildCommand'], 'npm run build');
|
||||
$this->assertEquals($framework['body']['outputDirectory'], './dist');
|
||||
|
||||
$framework = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/detections', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'content-type' => 'application/json',
|
||||
], $this->getHeaders()), [
|
||||
'providerRepositoryId' => $this->providerRepositoryId4,
|
||||
'type' => 'framework',
|
||||
'providerRootDirectory' => 'remix/starter'
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $framework['headers']['status-code']);
|
||||
$this->assertEquals($framework['body']['framework'], 'remix');
|
||||
$this->assertEquals($framework['body']['installCommand'], 'npm install');
|
||||
$this->assertEquals($framework['body']['buildCommand'], 'npm run build');
|
||||
$this->assertEquals($framework['body']['outputDirectory'], './build');
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
$framework = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/detections', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'content-type' => 'application/json',
|
||||
], $this->getHeaders()), [
|
||||
'providerRepositoryId' => 'randomRepositoryId', // Invalid repository ID
|
||||
'type' => 'framework',
|
||||
]);
|
||||
|
||||
$this->assertEquals(404, $framework['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testGitHubAuthorize
|
||||
*/
|
||||
@@ -94,7 +174,7 @@ class VCSConsoleClientTest extends Scope
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$runtime = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/contents', array_merge([
|
||||
$runtime = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/contents', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
@@ -126,7 +206,7 @@ class VCSConsoleClientTest extends Scope
|
||||
$this->assertTrue($libContent['isDirectory']);
|
||||
$this->assertEquals(0, $gemfileContent['size']);
|
||||
|
||||
$runtime = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/contents?providerRootDirectory=lib', array_merge([
|
||||
$runtime = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/' . $this->providerRepositoryId . '/contents?providerRootDirectory=lib', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
@@ -150,7 +230,7 @@ class VCSConsoleClientTest extends Scope
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
$runtime = $this->client->call(Client::METHOD_POST, '/vcs/github/installations/' . $installationId . '/providerRepositories/randomRepositoryId/contents', array_merge([
|
||||
$runtime = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories/randomRepositoryId/contents', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
@@ -168,26 +248,75 @@ class VCSConsoleClientTest extends Scope
|
||||
|
||||
$repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'runtime'
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $repositories['headers']['status-code']);
|
||||
$this->assertEquals($repositories['body']['total'], 3);
|
||||
$this->assertEquals($repositories['body']['providerRepositories'][0]['name'], 'function1.4');
|
||||
$this->assertEquals($repositories['body']['providerRepositories'][0]['organization'], 'appwrite-test');
|
||||
$this->assertEquals($repositories['body']['providerRepositories'][0]['provider'], 'github');
|
||||
$this->assertEquals($repositories['body']['providerRepositories'][1]['name'], 'appwrite');
|
||||
$this->assertEquals($repositories['body']['providerRepositories'][2]['name'], 'ruby-starter');
|
||||
|
||||
$this->assertEquals($repositories['body']['total'], 4);
|
||||
$this->assertEquals($repositories['body']['runtimeProviderRepositories'][0]['name'], 'starter-for-svelte');
|
||||
$this->assertEquals($repositories['body']['runtimeProviderRepositories'][0]['organization'], 'appwrite-test');
|
||||
$this->assertEquals($repositories['body']['runtimeProviderRepositories'][0]['provider'], 'github');
|
||||
$this->assertEquals($repositories['body']['runtimeProviderRepositories'][0]['runtime'], 'node-22');
|
||||
|
||||
$searchedRepositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'search' => 'func'
|
||||
'search' => 'function1.4',
|
||||
'type' => 'runtime'
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $searchedRepositories['headers']['status-code']);
|
||||
$this->assertEquals($searchedRepositories['body']['total'], 1);
|
||||
$this->assertEquals($searchedRepositories['body']['providerRepositories'][0]['name'], 'function1.4');
|
||||
$this->assertEquals($searchedRepositories['body']['runtimeProviderRepositories'][0]['name'], 'function1.4');
|
||||
$this->assertEquals($searchedRepositories['body']['runtimeProviderRepositories'][0]['runtime'], 'node-2');
|
||||
|
||||
$searchedRepositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'search' => 'appwrite',
|
||||
'type' => 'runtime'
|
||||
]);
|
||||
$this->assertEquals(200, $searchedRepositories['headers']['status-code']);
|
||||
$this->assertEquals($searchedRepositories['body']['total'], 1);
|
||||
$this->assertEquals($searchedRepositories['body']['runtimeProviderRepositories'][0]['name'], 'appwrite');
|
||||
$this->assertEquals($searchedRepositories['body']['runtimeProviderRepositories'][0]['runtime'], 'php-8.3');
|
||||
|
||||
$searchedRepositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'search' => 'ruby-starter',
|
||||
'type' => 'runtime'
|
||||
]);
|
||||
$this->assertEquals(200, $searchedRepositories['headers']['status-code']);
|
||||
$this->assertEquals($searchedRepositories['body']['total'], 1);
|
||||
$this->assertEquals($searchedRepositories['body']['runtimeProviderRepositories'][0]['name'], 'ruby-starter');
|
||||
$this->assertEquals($searchedRepositories['body']['runtimeProviderRepositories'][0]['runtime'], 'ruby-3.3');
|
||||
|
||||
$repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'framework'
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $repositories['headers']['status-code']);
|
||||
$this->assertEquals($repositories['body']['total'], 4);
|
||||
$this->assertEquals($repositories['body']['frameworkProviderRepositories'][0]['name'], 'starter-for-svelte');
|
||||
$this->assertEquals($repositories['body']['frameworkProviderRepositories'][0]['organization'], 'appwrite-test');
|
||||
$this->assertEquals($repositories['body']['frameworkProviderRepositories'][0]['provider'], 'github');
|
||||
$this->assertEquals($repositories['body']['frameworkProviderRepositories'][0]['framework'], 'sveltekit');
|
||||
|
||||
$searchedRepositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'search' => 'appwrite',
|
||||
'type' => 'runtime'
|
||||
]);
|
||||
$this->assertEquals(200, $searchedRepositories['headers']['status-code']);
|
||||
$this->assertEquals($searchedRepositories['body']['total'], 1);
|
||||
$this->assertEquals($searchedRepositories['body']['runtimeProviderRepositories'][0]['name'], 'appwrite');
|
||||
$this->assertEquals($searchedRepositories['body']['runtimeProviderRepositories'][0]['runtime'], 'other');
|
||||
|
||||
// TODO: If you are about to add another check, rewrite this to @provideScenarios
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
@@ -195,9 +324,29 @@ class VCSConsoleClientTest extends Scope
|
||||
|
||||
$repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/randomInstallationId/providerRepositories', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'runtime'
|
||||
]);
|
||||
|
||||
$this->assertEquals(404, $repositories['headers']['status-code']);
|
||||
|
||||
$repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'randomType'
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $repositories['headers']['status-code']);
|
||||
|
||||
$repositories = $this->client->call(Client::METHOD_GET, '/vcs/github/installations/' . $installationId . '/providerRepositories', array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'search' => 'randomSearch',
|
||||
'type' => 'framework'
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $repositories['headers']['status-code']);
|
||||
$this->assertEquals($repositories['body']['total'], 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
|
||||
export default defineConfig({
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "my-astro-app",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^5.2.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Astro static</title>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Main page</title>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user