Merge pull request #5230 from appwrite/feat-executor-v3-sync
Feat executor v3 sync
This commit is contained in:
commit
fd499265ce
17 changed files with 470 additions and 38 deletions
2
.env
2
.env
|
@ -47,6 +47,8 @@ _APP_SMTP_PORT=1025
|
|||
_APP_SMTP_SECURE=
|
||||
_APP_SMTP_USERNAME=
|
||||
_APP_SMTP_PASSWORD=
|
||||
_APP_HAMSTER_RECIPIENTS=
|
||||
_APP_HAMSTER_INTERVAL=86400
|
||||
_APP_SMS_PROVIDER=sms://username:password@mock
|
||||
_APP_SMS_FROM=+123456789
|
||||
_APP_STORAGE_LIMIT=30000000
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
- Increase Traefik TCP + file limits [#4673](https://github.com/appwrite/appwrite/pull/4673)
|
||||
- Fix invited account verified status [#4776](https://github.com/appwrite/appwrite/pull/4776)
|
||||
- Get default region from environment on project create [#4780](https://github.com/appwrite/appwrite/pull/4780)
|
||||
- Store build output file size [#4844](https://github.com/appwrite/appwrite/pull/4844)
|
||||
- Fix max mimetype size [#4814](https://github.com/appwrite/appwrite/pull/4814)
|
||||
|
||||
# Version 1.1.2
|
||||
|
|
|
@ -165,6 +165,7 @@ RUN chmod +x /usr/local/bin/doctor && \
|
|||
chmod +x /usr/local/bin/sdks && \
|
||||
chmod +x /usr/local/bin/specs && \
|
||||
chmod +x /usr/local/bin/ssl && \
|
||||
chmod +x /usr/local/bin/hamster && \
|
||||
chmod +x /usr/local/bin/test && \
|
||||
chmod +x /usr/local/bin/vars && \
|
||||
chmod +x /usr/local/bin/worker-audits && \
|
||||
|
|
|
@ -2672,6 +2672,17 @@ $collections = [
|
|||
'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,
|
||||
|
@ -2684,7 +2695,18 @@ $collections = [
|
|||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'deploymentInternalId',
|
||||
'$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,
|
||||
|
@ -2738,17 +2760,6 @@ $collections = [
|
|||
'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('stderr'),
|
||||
'type' => Database::VAR_STRING,
|
||||
|
|
|
@ -88,6 +88,11 @@ return [
|
|||
'description' => 'The request cannot be fulfilled with the current protocol. Please check the value of the _APP_OPTIONS_FORCE_HTTPS environment variable.',
|
||||
'code' => 500,
|
||||
],
|
||||
Exception::GENERAL_CODES_DISABLED => [
|
||||
'name' => Exception::GENERAL_CODES_DISABLED,
|
||||
'description' => 'Invitation codes are disabled on this server. Please contact the server administrator.',
|
||||
'code' => 500,
|
||||
],
|
||||
|
||||
/** User Errors */
|
||||
Exception::USER_COUNT_EXCEEDED => [
|
||||
|
@ -125,8 +130,8 @@ return [
|
|||
'description' => 'Console registration is restricted to specific emails. Contact your administrator for more information.',
|
||||
'code' => 401,
|
||||
],
|
||||
Exception::USER_CODE_INVALID => [
|
||||
'name' => Exception::USER_CODE_INVALID,
|
||||
Exception::USER_INVALID_CODE => [
|
||||
'name' => Exception::USER_INVALID_CODE,
|
||||
'description' => 'The specified code is not valid. Contact your administrator for more information.',
|
||||
'code' => 401,
|
||||
],
|
||||
|
|
|
@ -82,8 +82,12 @@ App::post('/v1/account/invite')
|
|||
|
||||
$whitelistCodes = (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_CODES', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_CODES', null)) : [];
|
||||
|
||||
if (empty($whitelistCodes)) {
|
||||
throw new Exception(Exception::GENERAL_CODES_DISABLED);
|
||||
}
|
||||
|
||||
if (!empty($whitelistCodes) && !\in_array($code, $whitelistCodes)) {
|
||||
throw new Exception(Exception::USER_CODE_INVALID);
|
||||
throw new Exception(Exception::USER_INVALID_CODE);
|
||||
}
|
||||
|
||||
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
|
||||
|
@ -170,7 +174,7 @@ App::post('/v1/account')
|
|||
$whitelistEmails = $project->getAttribute('authWhitelistEmails');
|
||||
$whitelistIPs = $project->getAttribute('authWhitelistIPs');
|
||||
|
||||
if (!empty($whitelistEmails) && !\in_array($email, $whitelistEmails)) {
|
||||
if (!empty($whitelistEmails) && !\in_array($email, $whitelistEmails) && !\in_array(strtoupper($email), $whitelistEmails)) {
|
||||
throw new Exception(Exception::USER_EMAIL_NOT_WHITELISTED);
|
||||
}
|
||||
|
||||
|
|
|
@ -101,13 +101,14 @@ class BuildsV1 extends Worker
|
|||
'deploymentId' => $deployment->getId(),
|
||||
'status' => 'processing',
|
||||
'path' => '',
|
||||
'size' => 0,
|
||||
'runtime' => $function->getAttribute('runtime'),
|
||||
'source' => $deployment->getAttribute('path'),
|
||||
'sourceType' => $device,
|
||||
'stdout' => '',
|
||||
'stderr' => '',
|
||||
'duration' => 0
|
||||
'endTime' => null,
|
||||
'duration' => 0,
|
||||
'size' => 0
|
||||
]));
|
||||
$deployment->setAttribute('buildId', $build->getId());
|
||||
$deployment->setAttribute('buildInternalId', $build->getInternalId());
|
||||
|
@ -189,8 +190,11 @@ class BuildsV1 extends Worker
|
|||
]
|
||||
);
|
||||
|
||||
$endTime = DateTime::now();
|
||||
|
||||
/** Update the build document */
|
||||
$build->setAttribute('startTime', DateTime::format((new \DateTime())->setTimestamp($response['startTime'])));
|
||||
$build->setAttribute('endTime', $endTime);
|
||||
$build->setAttribute('duration', \intval(\ceil($response['duration'])));
|
||||
$build->setAttribute('status', 'ready');
|
||||
$build->setAttribute('path', $response['path']);
|
||||
|
@ -226,7 +230,7 @@ class BuildsV1 extends Worker
|
|||
} catch (\Throwable $th) {
|
||||
$endTime = DateTime::now();
|
||||
$interval = (new \DateTime($endTime))->diff(new \DateTime($startTime));
|
||||
|
||||
$build->setAttribute('endTime', $endTime);
|
||||
$build->setAttribute('duration', $interval->format('%s') + 0);
|
||||
$build->setAttribute('status', 'failed');
|
||||
$build->setAttribute('stderr', $th->getMessage());
|
||||
|
@ -262,7 +266,6 @@ class BuildsV1 extends Worker
|
|||
->setParam('builds.{scope}.compute', 1)
|
||||
->setParam('buildStatus', $build->getAttribute('status', ''))
|
||||
->setParam('buildTime', $build->getAttribute('duration'))
|
||||
->setParam('buildSize', $build->getAttribute('size'))
|
||||
->setParam('networkRequestSize', 0)
|
||||
->setParam('networkResponseSize', 0)
|
||||
->submit();
|
||||
|
|
3
bin/hamster
Normal file
3
bin/hamster
Normal file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php hamster $@
|
|
@ -73,7 +73,8 @@
|
|||
"phpmailer/phpmailer": "6.6.0",
|
||||
"chillerlan/php-qrcode": "4.3.3",
|
||||
"adhocore/jwt": "1.1.2",
|
||||
"slickdeals/statsd": "3.1.0"
|
||||
"slickdeals/statsd": "3.1.0",
|
||||
"league/csv": "^9.0.0"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
|
|
89
composer.lock
generated
89
composer.lock
generated
|
@ -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": "e8e64d67c559b26264b6a89ebab1ab72",
|
||||
"content-hash": "d388807afe22fb473b5136a75cb3d7e7",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
|
@ -870,6 +870,93 @@
|
|||
},
|
||||
"time": "2022-11-29T16:25:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/csv",
|
||||
"version": "9.9.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/csv.git",
|
||||
"reference": "b4418ede47fbd88facc34e40a16c8ce9153b961b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/csv/zipball/b4418ede47fbd88facc34e40a16c8ce9153b961b",
|
||||
"reference": "b4418ede47fbd88facc34e40a16c8ce9153b961b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"php": "^8.1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/collections": "^2.1.2",
|
||||
"ext-dom": "*",
|
||||
"ext-xdebug": "*",
|
||||
"friendsofphp/php-cs-fixer": "^v3.14.3",
|
||||
"phpbench/phpbench": "^1.2.8",
|
||||
"phpstan/phpstan": "^1.10.4",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.1.2",
|
||||
"phpstan/phpstan-phpunit": "^1.3.10",
|
||||
"phpstan/phpstan-strict-rules": "^1.5.0",
|
||||
"phpunit/phpunit": "^10.0.14"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-dom": "Required to use the XMLConverter and the HTMLConverter classes",
|
||||
"ext-iconv": "Needed to ease transcoding CSV using iconv stream filters"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "9.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"League\\Csv\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ignace Nyamagana Butera",
|
||||
"email": "nyamsprod@gmail.com",
|
||||
"homepage": "https://github.com/nyamsprod/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "CSV data manipulation made easy in PHP",
|
||||
"homepage": "https://csv.thephpleague.com",
|
||||
"keywords": [
|
||||
"convert",
|
||||
"csv",
|
||||
"export",
|
||||
"filter",
|
||||
"import",
|
||||
"read",
|
||||
"transform",
|
||||
"write"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://csv.thephpleague.com",
|
||||
"issues": "https://github.com/thephpleague/csv/issues",
|
||||
"rss": "https://github.com/thephpleague/csv/releases.atom",
|
||||
"source": "https://github.com/thephpleague/csv"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/sponsors/nyamsprod",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2023-03-11T15:57:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "matomo/device-detector",
|
||||
"version": "6.0.0",
|
||||
|
|
|
@ -59,7 +59,7 @@ services:
|
|||
DEBUG: false
|
||||
TESTING: true
|
||||
VERSION: dev
|
||||
VITE_CONSOLE_MODE: self-hosted
|
||||
VITE_CONSOLE_MODE: cloud
|
||||
ports:
|
||||
- 9501:80
|
||||
networks:
|
||||
|
@ -145,6 +145,7 @@ services:
|
|||
- _APP_SMTP_SECURE
|
||||
- _APP_SMTP_USERNAME
|
||||
- _APP_SMTP_PASSWORD
|
||||
- _APP_HAMSTER_RECIPIENTS
|
||||
- _APP_USAGE_STATS
|
||||
- _APP_INFLUXDB_HOST
|
||||
- _APP_INFLUXDB_PORT
|
||||
|
@ -554,6 +555,39 @@ services:
|
|||
- _APP_LOGGING_PROVIDER
|
||||
- _APP_LOGGING_CONFIG
|
||||
|
||||
appwrite-hamster:
|
||||
entrypoint: hamster
|
||||
<<: *x-logging
|
||||
container_name: appwrite-hamster
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
depends_on:
|
||||
- redis
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_CONNECTIONS_MAX
|
||||
- _APP_POOL_CLIENTS
|
||||
- _APP_OPENSSL_KEY_V1
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_CONNECTIONS_DB_CONSOLE
|
||||
- _APP_CONNECTIONS_DB_PROJECT
|
||||
- _APP_CONNECTIONS_CACHE
|
||||
- _APP_HAMSTER_RECIPIENTS
|
||||
- _APP_HAMSTER_INTERVAL
|
||||
|
||||
appwrite-maintenance:
|
||||
entrypoint: maintenance
|
||||
<<: *x-logging
|
||||
|
|
|
@ -50,6 +50,7 @@ class Exception extends \Exception
|
|||
public const GENERAL_CURSOR_NOT_FOUND = 'general_cursor_not_found';
|
||||
public const GENERAL_SERVER_ERROR = 'general_server_error';
|
||||
public const GENERAL_PROTOCOL_UNSUPPORTED = 'general_protocol_unsupported';
|
||||
public const GENERAL_CODES_DISABLED = 'general_codes_disabled';
|
||||
|
||||
/** Users */
|
||||
public const USER_COUNT_EXCEEDED = 'user_count_exceeded';
|
||||
|
@ -60,7 +61,7 @@ class Exception extends \Exception
|
|||
public const USER_PASSWORD_RESET_REQUIRED = 'user_password_reset_required';
|
||||
public const USER_EMAIL_NOT_WHITELISTED = 'user_email_not_whitelisted';
|
||||
public const USER_IP_NOT_WHITELISTED = 'user_ip_not_whitelisted';
|
||||
public const USER_CODE_INVALID = 'user_code_invalid';
|
||||
public const USER_INVALID_CODE = 'user_invalid_code';
|
||||
public const USER_INVALID_CREDENTIALS = 'user_invalid_credentials';
|
||||
public const USER_ANONYMOUS_CONSOLE_PROHIBITED = 'user_anonymous_console_prohibited';
|
||||
public const USER_SESSION_ALREADY_EXISTS = 'user_session_already_exists';
|
||||
|
@ -186,6 +187,7 @@ class Exception extends \Exception
|
|||
public const PLATFORM_NOT_FOUND = 'platform_not_found';
|
||||
|
||||
protected $type = '';
|
||||
protected $errors = [];
|
||||
|
||||
public function __construct(string $type = Exception::GENERAL_UNKNOWN, string $message = null, int $code = null, \Throwable $previous = null)
|
||||
{
|
||||
|
|
|
@ -46,17 +46,6 @@ class V17 extends Migration
|
|||
$this->projectDB->setNamespace("_{$this->project->getInternalId()}");
|
||||
|
||||
switch ($id) {
|
||||
case 'files':
|
||||
try {
|
||||
/**
|
||||
* Update 'mimeType' attribute size (127->255)
|
||||
*/
|
||||
$this->projectDB->updateAttribute($id, 'mimeType', Database::VAR_STRING, 255, true, false);
|
||||
$this->projectDB->deleteCachedCollection($id);
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'mimeType' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
break;
|
||||
case 'builds':
|
||||
try {
|
||||
/**
|
||||
|
@ -68,6 +57,18 @@ class V17 extends Migration
|
|||
Console::warning("'size' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
break;
|
||||
case 'files':
|
||||
try {
|
||||
/**
|
||||
* Update 'mimeType' attribute size (127->255)
|
||||
*/
|
||||
$this->projectDB->updateAttribute($id, 'mimeType', Database::VAR_STRING, 255, true, false);
|
||||
$this->projectDB->deleteCachedCollection($id);
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'mimeType' from {$id}: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
try {
|
||||
/**
|
||||
* Delete 'endTime' attribute (use startTime+duration if needed)
|
||||
|
|
|
@ -12,6 +12,7 @@ use Appwrite\Platform\Tasks\PatchCreateMissingSchedules;
|
|||
use Appwrite\Platform\Tasks\SDKs;
|
||||
use Appwrite\Platform\Tasks\Specs;
|
||||
use Appwrite\Platform\Tasks\SSL;
|
||||
use Appwrite\Platform\Tasks\Hamster;
|
||||
use Appwrite\Platform\Tasks\Usage;
|
||||
use Appwrite\Platform\Tasks\Vars;
|
||||
use Appwrite\Platform\Tasks\Version;
|
||||
|
@ -27,6 +28,7 @@ class Tasks extends Service
|
|||
->addAction(Usage::getName(), new Usage())
|
||||
->addAction(Vars::getName(), new Vars())
|
||||
->addAction(SSL::getName(), new SSL())
|
||||
->addAction(Hamster::getName(), new Hamster())
|
||||
->addAction(Doctor::getName(), new Doctor())
|
||||
->addAction(Install::getName(), new Install())
|
||||
->addAction(Maintenance::getName(), new Maintenance())
|
||||
|
|
271
src/Appwrite/Platform/Tasks/Hamster.php
Normal file
271
src/Appwrite/Platform/Tasks/Hamster.php
Normal file
|
@ -0,0 +1,271 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Exception;
|
||||
use Utopia\App;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use League\Csv\Writer;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Registry\Registry;
|
||||
|
||||
class Hamster extends Action
|
||||
{
|
||||
private array $columns = [
|
||||
'Project ID',
|
||||
'Project Name',
|
||||
'Functions',
|
||||
'Deployments',
|
||||
'Members',
|
||||
'Domains',
|
||||
'Files',
|
||||
'Buckets',
|
||||
'Databases',
|
||||
'Documents',
|
||||
'Collections',
|
||||
'Storage',
|
||||
'Requests',
|
||||
'Bandwidth',
|
||||
'Users',
|
||||
'Sessions',
|
||||
'Executions'
|
||||
];
|
||||
|
||||
private array $usageStats = [
|
||||
'Files' => 'files.$all.count.total',
|
||||
'Buckets' => 'buckets.$all.count.total',
|
||||
'Databases' => 'databases.$all.count.total',
|
||||
'Documents' => 'documents.$all.count.total',
|
||||
'Collections' => 'collections.$all.count.total',
|
||||
'Storage' => 'project.$all.storage.size',
|
||||
'Requests' => 'project.$all.network.requests',
|
||||
'Bandwidth' => 'project.$all.network.bandwidth',
|
||||
'Users' => 'users.$all.count.total',
|
||||
'Sessions' => 'sessions.$all.requests.create',
|
||||
'Executions' => 'executions.$all.compute.total',
|
||||
];
|
||||
|
||||
protected string $directory = '/usr/local';
|
||||
protected string $path;
|
||||
|
||||
protected string $date;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'hamster';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->desc('Get stats for projects')
|
||||
->inject('register')
|
||||
->inject('pools')
|
||||
->inject('cache')
|
||||
->inject('dbForConsole')
|
||||
->callback(function (Registry $register, Group $pools, Cache $cache, Database $dbForConsole) {
|
||||
$this->action($register, $pools, $cache, $dbForConsole);
|
||||
});
|
||||
}
|
||||
|
||||
private function getStats(Database $dbForConsole, Database $dbForProject, Document $project): array
|
||||
{
|
||||
$stats = [];
|
||||
|
||||
/** Get Project ID */
|
||||
$stats['Project ID'] = $project->getId();
|
||||
|
||||
/** Get Project Name */
|
||||
$stats['Project Name'] = $project->getAttribute('name');
|
||||
|
||||
/** Get Total Functions */
|
||||
$stats['Functions'] = $dbForProject->count('functions', [], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get Total Deployments */
|
||||
$stats['Deployments'] = $dbForProject->count('deployments', [], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get Total Members */
|
||||
$teamInternalId = $project->getAttribute('teamInternalId', null);
|
||||
if ($teamInternalId) {
|
||||
$stats['Members'] = $dbForConsole->count('memberships', [
|
||||
Query::equal('teamInternalId', [$teamInternalId])
|
||||
], APP_LIMIT_COUNT);
|
||||
} else {
|
||||
$stats['Members'] = 0;
|
||||
}
|
||||
|
||||
/** Get Domains */
|
||||
$stats['Domains'] = $dbForProject->count('domains', [], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get Usage stats */
|
||||
$range = '90d';
|
||||
$periods = [
|
||||
'90d' => [
|
||||
'period' => '1d',
|
||||
'limit' => 90,
|
||||
],
|
||||
];
|
||||
|
||||
$metrics = array_values($this->usageStats);
|
||||
Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$limit = $periods[$range]['limit'];
|
||||
$period = $periods[$range]['period'];
|
||||
|
||||
$requestDocs = $dbForProject->find('stats', [
|
||||
Query::equal('period', [$period]),
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::limit($limit),
|
||||
Query::orderDesc('time'),
|
||||
]);
|
||||
|
||||
$stats[$metric] = [];
|
||||
foreach ($requestDocs as $requestDoc) {
|
||||
$stats[$metric][] = [
|
||||
'value' => $requestDoc->getAttribute('value'),
|
||||
'date' => $requestDoc->getAttribute('time'),
|
||||
];
|
||||
}
|
||||
|
||||
$stats[$metric] = array_reverse($stats[$metric]);
|
||||
// Calculate aggregate of each metric
|
||||
$stats[$metric] = array_sum(array_column($stats[$metric], 'value'));
|
||||
}
|
||||
});
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
public function action(Registry $register, Group $pools, Cache $cache, Database $dbForConsole): void
|
||||
{
|
||||
|
||||
Console::title('Cloud Hamster V1');
|
||||
Console::success(APP_NAME . ' cloud hamster process v1 has started');
|
||||
|
||||
$interval = (int) App::getEnv('_APP_HAMSTER_INTERVAL', '30'); // 30 seconds (by default)
|
||||
|
||||
Console::loop(function () use ($register, $pools, $cache, $dbForConsole, $interval) {
|
||||
|
||||
$now = date('d-m-Y H:i:s', time());
|
||||
Console::info("[{$now}] Getting Cloud Usage Stats every {$interval} seconds");
|
||||
$loopStart = microtime(true);
|
||||
|
||||
/* Initialise new Utopia app */
|
||||
$app = new App('UTC');
|
||||
$console = $app->getResource('console');
|
||||
|
||||
/** CSV stuff */
|
||||
$this->date = date('Y-m-d');
|
||||
$this->path = "{$this->directory}/stats_{$this->date}.csv";
|
||||
$csv = Writer::createFromPath($this->path, 'w');
|
||||
$csv->insertOne($this->columns);
|
||||
|
||||
/** Database connections */
|
||||
$totalProjects = $dbForConsole->count('projects') + 1;
|
||||
Console::success("Found a total of: {$totalProjects} projects");
|
||||
|
||||
$projects = [$console];
|
||||
$count = 0;
|
||||
$limit = 30;
|
||||
$sum = 30;
|
||||
$offset = 0;
|
||||
while (!empty($projects)) {
|
||||
foreach ($projects as $project) {
|
||||
/**
|
||||
* Skip user projects with id 'console'
|
||||
*/
|
||||
if ($project->getId() === 'console') {
|
||||
continue;
|
||||
}
|
||||
|
||||
Console::info("Getting stats for {$project->getId()}");
|
||||
|
||||
try {
|
||||
$db = $project->getAttribute('database');
|
||||
$adapter = $pools
|
||||
->get($db)
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
$dbForProject = new Database($adapter, $cache);
|
||||
$dbForProject->setDefaultDatabase('appwrite');
|
||||
$dbForProject->setNamespace('_' . $project->getInternalId());
|
||||
|
||||
$statsPerProject = $this->getStats($dbForConsole, $dbForProject, $project);
|
||||
$csv->insertOne(array_values($statsPerProject));
|
||||
} catch (\Throwable $th) {
|
||||
throw $th;
|
||||
Console::error('Failed to update project ("' . $project->getId() . '") version with error: ' . $th->getMessage());
|
||||
} finally {
|
||||
$pools
|
||||
->get($db)
|
||||
->reclaim();
|
||||
}
|
||||
}
|
||||
|
||||
$sum = \count($projects);
|
||||
|
||||
$projects = $dbForConsole->find('projects', [
|
||||
Query::limit($limit),
|
||||
Query::offset($offset),
|
||||
]);
|
||||
|
||||
$offset = $offset + $limit;
|
||||
$count = $count + $sum;
|
||||
|
||||
Console::log('Iterated through ' . $count . '/' . $totalProjects . ' projects...');
|
||||
}
|
||||
|
||||
$this->sendEmail($register);
|
||||
|
||||
$pools
|
||||
->get('console')
|
||||
->reclaim();
|
||||
|
||||
$loopTook = microtime(true) - $loopStart;
|
||||
$now = date('d-m-Y H:i:s', time());
|
||||
Console::info("[{$now}] Cloud Stats took {$loopTook} seconds");
|
||||
}, $interval);
|
||||
}
|
||||
|
||||
private function sendEmail(Registry $register)
|
||||
{
|
||||
/** @var \PHPMailer\PHPMailer\PHPMailer $mail */
|
||||
$mail = $register->get('smtp');
|
||||
|
||||
$mail->clearAddresses();
|
||||
$mail->clearAllRecipients();
|
||||
$mail->clearReplyTos();
|
||||
$mail->clearAttachments();
|
||||
$mail->clearBCCs();
|
||||
$mail->clearCCs();
|
||||
|
||||
try {
|
||||
/** Addresses */
|
||||
$mail->setFrom(App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), 'Appwrite Cloud Hamster');
|
||||
$recipients = explode(',', App::getEnv('_APP_HAMSTER_RECIPIENTS', ''));
|
||||
foreach ($recipients as $recipient) {
|
||||
$mail->addAddress($recipient);
|
||||
}
|
||||
|
||||
/** Attachments */
|
||||
$mail->addAttachment($this->path);
|
||||
|
||||
/** Content */
|
||||
$mail->Subject = "Cloud Report for {$this->date}";
|
||||
$mail->Body = "Please find the daily cloud report atttached";
|
||||
|
||||
$mail->send();
|
||||
Console::success('Email has been sent!');
|
||||
} catch (Exception $e) {
|
||||
Console::error("Message could not be sent. Mailer Error: {$mail->ErrorInfo}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -51,6 +51,12 @@ class Build extends Model
|
|||
'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.',
|
||||
|
|
|
@ -38,7 +38,7 @@ class AccountConsoleClientTest extends Scope
|
|||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 401);
|
||||
$this->assertEquals($response['body']['type'], Exception::USER_CODE_INVALID);
|
||||
$this->assertEquals($response['body']['type'], Exception::USER_INVALID_CODE);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/invite', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
|
@ -52,7 +52,7 @@ class AccountConsoleClientTest extends Scope
|
|||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 401);
|
||||
$this->assertEquals($response['body']['type'], Exception::USER_CODE_INVALID);
|
||||
$this->assertEquals($response['body']['type'], Exception::USER_INVALID_CODE);
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
|
|
Loading…
Reference in a new issue