Merge branch '1.7.x' into feat-ref-param-to-vcs-list-contents

This commit is contained in:
Khushboo Verma 2025-06-13 15:51:35 +05:30
commit 4099f8968a
10 changed files with 63 additions and 251 deletions

View file

@ -251,7 +251,7 @@ CLI::setResource('logError', function (Registry $register) {
};
}, ['register']);
CLI::setResource('executor', fn () => new Executor(fn (string $projectId, string $deploymentId) => System::getEnv('_APP_EXECUTOR_HOST')));
CLI::setResource('executor', fn () => new Executor());
CLI::setResource('telemetry', fn () => new NoTelemetry());

View file

@ -59,7 +59,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '16.1.0',
'version' => '17.0.1',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@ -77,7 +77,7 @@ return [
[
'key' => 'apple',
'name' => 'Apple',
'version' => '10.1.0',
'version' => '10.1.1',
'url' => 'https://github.com/appwrite/sdk-for-apple',
'package' => 'https://github.com/appwrite/sdk-for-apple',
'enabled' => true,
@ -217,7 +217,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '7.0.0',
'version' => '8.0.0',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@ -231,6 +231,11 @@ return [
'gitRepoName' => 'sdk-for-cli',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
'exclude' => [
'services' => [
['name' => 'assistant'],
],
],
],
],
],
@ -411,7 +416,7 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
'version' => '10.0.0',
'version' => '10.1.0',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,

View file

@ -845,15 +845,18 @@ App::get('/v1/health/storage')
$checkStart = \microtime(true);
foreach ($devices as $device) {
if (!$device->write($device->getPath('health.txt'), 'test', 'text/plain')) {
$uniqueFileName = \uniqid('health', true);
$filePath = $device->getPath($uniqueFileName);
if (!$device->write($filePath, 'test', 'text/plain')) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed writing test file to ' . $device->getRoot());
}
if ($device->read($device->getPath('health.txt')) !== 'test') {
if ($device->read($filePath) !== 'test') {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed reading test file from ' . $device->getRoot());
}
if (!$device->delete($device->getPath('health.txt'))) {
if (!$device->delete($filePath)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed deleting test file from ' . $device->getRoot());
}
}

View file

@ -880,7 +880,7 @@ App::setResource('apiKey', function (Request $request, Document $project): ?Key
return Key::decode($project, $key);
}, ['request', 'project']);
App::setResource('executor', fn () => new Executor(fn (string $projectId, string $deploymentId) => System::getEnv('_APP_EXECUTOR_HOST')));
App::setResource('executor', fn () => new Executor());
App::setResource('resourceToken', function ($project, $dbForProject, $request) {
$tokenJWT = $request->getParam('token');

View file

@ -402,7 +402,7 @@ Server::setResource('logError', function (Registry $register, Document $project)
};
}, ['register', 'project']);
Server::setResource('executor', fn () => new Executor(fn (string $projectId, string $deploymentId) => System::getEnv('_APP_EXECUTOR_HOST')));
Server::setResource('executor', fn () => new Executor());
$pools = $register->get('pools');
$platform = new Appwrite();

View file

@ -877,6 +877,10 @@ class Builds extends Action
}
}
$deployment->setAttribute('buildLogs', $logs);
$this->afterBuildSuccess($dbForProject, $deployment);
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment);
$queueForRealtime
@ -1313,6 +1317,19 @@ class Builds extends Action
->trigger();
}
/**
* Hook to run after build success
*
* @param Database $dbForProject
* @param Document $deployment
* @return void
*/
protected function afterBuildSuccess(Database $dbForProject, Document &$deployment): void
{
assert($dbForProject instanceof Database);
assert($deployment instanceof Document);
}
protected function getRuntime(Document $resource, string $version): array
{
$runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []);

View file

@ -4,9 +4,11 @@ namespace Appwrite\Platform\Tasks;
use Appwrite\Event\Certificate;
use Appwrite\Event\Delete;
use DateInterval;
use DateTime;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\DateTime as DatabaseDateTime;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Platform\Action;
@ -58,29 +60,35 @@ class Maintenance extends Action
Console::info('Setting loop start time to ' . $next->format("Y-m-d H:i:s.v") . '. Delaying for ' . $delay . ' seconds.');
Console::loop(function () use ($interval, $cacheRetention, $schedulesDeletionRetention, $usageStatsRetentionHourly, $dbForPlatform, $console, $queueForDeletes, $queueForCertificates) {
$time = DateTime::now();
$time = DatabaseDateTime::now();
Console::info("[{$time}] Notifying workers with maintenance tasks every {$interval} seconds");
// Iterate through project only if it was accessed in last 30 days
$dateInterval = DateInterval::createFromDateString('30 days');
$before30days = (new DateTime())->sub($dateInterval);
$dbForPlatform->foreach(
'projects',
function (Document $project) use ($queueForDeletes, $usageStatsRetentionHourly) {
$queueForDeletes
->setType(DELETE_TYPE_MAINTENANCE)
->setProject($project)
->setUsageRetentionHourlyDateTime(DateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly))
->setUsageRetentionHourlyDateTime(DatabaseDateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly))
->trigger();
},
[
Query::equal('region', [System::getEnv('_APP_REGION', 'default')]),
Query::limit(100),
Query::greaterThanEqual('accessedAt', DatabaseDateTime::format($before30days)),
Query::orderAsc('teamInternalId'),
]
);
$queueForDeletes
->setType(DELETE_TYPE_MAINTENANCE)
->setProject($console)
->setUsageRetentionHourlyDateTime(DateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly))
->setUsageRetentionHourlyDateTime(DatabaseDateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly))
->trigger();
$this->notifyDeleteConnections($queueForDeletes);
@ -94,13 +102,13 @@ class Maintenance extends Action
{
$queueForDeletes
->setType(DELETE_TYPE_REALTIME)
->setDatetime(DateTime::addSeconds(new \DateTime(), -60))
->setDatetime(DatabaseDateTime::addSeconds(new \DateTime(), -60))
->trigger();
}
private function renewCertificates(Database $dbForPlatform, Certificate $queueForCertificate): void
{
$time = DateTime::now();
$time = DatabaseDateTime::now();
$certificates = $dbForPlatform->find('certificates', [
Query::lessThan('attempts', 5), // Maximum 5 attempts
@ -129,7 +137,7 @@ class Maintenance extends Action
{
$queueForDeletes
->setType(DELETE_TYPE_CACHE_BY_TIMESTAMP)
->setDatetime(DateTime::addSeconds(new \DateTime(), -1 * $interval))
->setDatetime(DatabaseDateTime::addSeconds(new \DateTime(), -1 * $interval))
->trigger();
}
@ -137,7 +145,7 @@ class Maintenance extends Action
{
$queueForDeletes
->setType(DELETE_TYPE_SCHEDULES)
->setDatetime(DateTime::addSeconds(new \DateTime(), -1 * $interval))
->setDatetime(DatabaseDateTime::addSeconds(new \DateTime(), -1 * $interval))
->trigger();
}
}

View file

@ -251,7 +251,8 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
->setDiscord(APP_SOCIAL_DISCORD_CHANNEL, APP_SOCIAL_DISCORD)
->setDefaultHeaders([
'X-Appwrite-Response-Format' => '1.7.0',
]);
])
->setExclude($language['exclude'] ?? []);
// Make sure we have a clean slate.
// Otherwise, all files in this dir will be pushed,

View file

@ -1,206 +0,0 @@
<?php
namespace Appwrite\Platform\Workers;
use Appwrite\Extend\Exception;
use Utopia\CLI\Console;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Queue\Message;
use Utopia\Registry\Registry;
use Utopia\System\System;
/**
* TODO remove later
*/
class StatsUsageDump extends Action
{
public const METRIC_COLLECTION_LEVEL_STORAGE = 4;
public const METRIC_DATABASE_LEVEL_STORAGE = 3;
public const METRIC_PROJECT_LEVEL_STORAGE = 2;
protected array $stats = [];
protected Registry $register;
/**
* Metrics to skip writing to logsDB
* As these metrics are calculated separately
* by logs DB
* @var array
*/
protected array $skipBaseMetrics = [
METRIC_DATABASES => true,
METRIC_BUCKETS => true,
METRIC_USERS => true,
METRIC_FUNCTIONS => true,
METRIC_TEAMS => true,
METRIC_MESSAGES => true,
METRIC_MAU => true,
METRIC_WEBHOOKS => true,
METRIC_PLATFORMS => true,
METRIC_PROVIDERS => true,
METRIC_TOPICS => true,
METRIC_KEYS => true,
METRIC_FILES => true,
METRIC_FILES_STORAGE => true,
METRIC_DEPLOYMENTS_STORAGE => true,
METRIC_BUILDS_STORAGE => true,
METRIC_DEPLOYMENTS => true,
METRIC_BUILDS => true,
METRIC_COLLECTIONS => true,
METRIC_DOCUMENTS => true,
METRIC_DATABASES_STORAGE => true,
];
/**
* Skip metrics associated with parent IDs
* these need to be checked individually with `str_ends_with`
*/
protected array $skipParentIdMetrics = [
'.files',
'.files.storage',
'.collections',
'.documents',
'.deployments',
'.deployments.storage',
'.builds',
'.builds.storage',
'.databases.storage'
];
/**
* @var callable(Document): Database
*/
protected $getLogsDB;
protected array $periods = [
'1h' => 'Y-m-d H:00',
'1d' => 'Y-m-d 00:00',
'inf' => '0000-00-00 00:00'
];
public static function getName(): string
{
return 'stats-usage-dump';
}
/**
* @throws \Exception
*/
public function __construct()
{
$this
->inject('message')
->inject('getProjectDB')
->inject('getLogsDB')
->inject('register')
->callback([$this, 'action']);
}
/**
* @param Message $message
* @param callable $getProjectDB
* @param callable $getLogsDB
* @param Registry $register
* @return void
* @throws Exception
* @throws \Throwable
* @throws \Utopia\Database\Exception
*/
public function action(Message $message, callable $getProjectDB, callable $getLogsDB, Registry $register): void
{
$this->getLogsDB = $getLogsDB;
$this->register = $register;
$payload = $message->getPayload() ?? [];
if (empty($payload)) {
throw new Exception('Missing payload');
}
foreach ($payload['stats'] ?? [] as $stats) {
$project = new Document($stats['project'] ?? []);
$numberOfKeys = !empty($stats['keys']) ? count($stats['keys']) : 0;
$receivedAt = $stats['receivedAt'] ?? null;
if ($numberOfKeys === 0) {
continue;
}
Console::log('['.DateTime::now().'] Id: '.$project->getId(). ' InternalId: '.$project->getInternalId(). ' Db: '.$project->getAttribute('database').' ReceivedAt: '.$receivedAt. ' Keys: '.$numberOfKeys);
try {
/** @var Database $dbForProject */
$dbForProject = $getProjectDB($project);
foreach ($stats['keys'] ?? [] as $key => $value) {
if ($value == 0) {
continue;
}
if (str_contains($key, METRIC_DATABASES_STORAGE)) {
continue;
}
foreach ($this->periods as $period => $format) {
$time = null;
if ($period !== 'inf') {
$time = !empty($receivedAt) ? (new \DateTime($receivedAt))->format($format) : date($format, time());
}
$id = \md5("{$time}_{$period}_{$key}");
$document = new Document([
'$id' => $id,
'period' => $period,
'time' => $time,
'metric' => $key,
'value' => $value,
'region' => System::getEnv('_APP_REGION', 'default'),
]);
$documentClone = clone $document;
$dbForProject->createOrUpdateDocumentsWithIncrease(
'stats',
'value',
[$document]
);
$this->writeToLogsDB($project, $documentClone);
}
}
} catch (\Exception $e) {
Console::error('[' . DateTime::now() . '] project [' . $project->getInternalId() . '] database [' . $project['database'] . '] ' . ' ' . $e->getMessage());
}
}
}
protected function writeToLogsDB(Document $project, Document $document): void
{
if (System::getEnv('_APP_STATS_USAGE_DUAL_WRITING', 'disabled') === 'disabled') {
Console::log('Dual Writing is disabled. Skipping...');
return;
}
if (array_key_exists($document->getAttribute('metric'), $this->skipBaseMetrics)) {
return;
}
foreach ($this->skipParentIdMetrics as $skipMetric) {
if (str_ends_with($document->getAttribute('metric'), $skipMetric)) {
return;
}
}
$dbForLogs = ($this->getLogsDB)($project);
try {
$dbForLogs->createOrUpdateDocumentsWithIncrease(
'stats',
'value',
[$document]
);
Console::success('Usage logs pushed to Logs DB');
} catch (\Throwable $th) {
Console::error($th->getMessage());
}
}
}

View file

@ -19,21 +19,14 @@ class Executor
public const METHOD_CONNECT = 'CONNECT';
public const METHOD_TRACE = 'TRACE';
private bool $selfSigned = false;
/**
* @var callable(string, string): string $endpoint
*/
private $endpointSelector;
protected bool $selfSigned = false;
protected string $endpoint;
protected array $headers;
/**
* @param callable(string, string): string $endpointSelector
*/
public function __construct(callable $endpointSelector)
public function __construct()
{
$this->endpointSelector = $endpointSelector;
$this->endpoint = System::getEnv('_APP_EXECUTOR_HOST', '');
$this->headers = [
'content-type' => 'application/json',
'authorization' => 'Bearer ' . System::getEnv('_APP_EXECUTOR_SECRET', ''),
@ -97,8 +90,8 @@ class Executor
'outputDirectory' => $outputDirectory
];
$endpoint = $this->selectEndpoint($projectId, $deploymentId);
$response = $this->call($endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout);
$response = $this->call($this->endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout);
$status = $response['headers']['status-code'];
if ($status >= 400) {
@ -128,8 +121,7 @@ class Executor
'timeout' => $timeout
];
$endpoint = $this->selectEndpoint($projectId, $deploymentId);
$this->call($endpoint, self::METHOD_GET, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout, $callback);
$this->call($this->endpoint, self::METHOD_GET, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout, $callback);
}
/**
@ -145,8 +137,7 @@ class Executor
$runtimeId = "$projectId-$deploymentId" . $suffix;
$route = "/runtimes/$runtimeId";
$endpoint = $this->selectEndpoint($projectId, $deploymentId);
$response = $this->call($endpoint, self::METHOD_DELETE, $route, [
$response = $this->call($this->endpoint, self::METHOD_DELETE, $route, [
'x-opr-addressing-method' => 'broadcast'
], [], true, 30);
@ -239,8 +230,7 @@ class Executor
$requestTimeout = $timeout + 15;
}
$endpoint = $this->selectEndpoint($projectId, $deploymentId);
$response = $this->call($endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId, 'content-type' => 'multipart/form-data', 'accept' => 'multipart/form-data' ], $params, true, $requestTimeout);
$response = $this->call($this->endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId, 'content-type' => 'multipart/form-data', 'accept' => 'multipart/form-data' ], $params, true, $requestTimeout);
$status = $response['headers']['status-code'];
if ($status >= 400) {
@ -274,8 +264,7 @@ class Executor
'timeout' => $timeout
];
$endpoint = $this->selectEndpoint($projectId, $deploymentId);
$response = $this->call($endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout);
$response = $this->call($this->endpoint, self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $timeout);
$status = $response['headers']['status-code'];
if ($status >= 400) {
@ -465,9 +454,4 @@ class Executor
return $output;
}
private function selectEndpoint(string $projectId, string $deploymentId): string
{
return call_user_func($this->endpointSelector, $projectId, $deploymentId);
}
}