diff --git a/composer.json b/composer.json index f08d01a..62f97f4 100644 --- a/composer.json +++ b/composer.json @@ -25,11 +25,11 @@ "ext-json": "*", "ext-swoole": "*", "utopia-php/config": "^0.2.2", - "utopia-php/console": "0.0.*", + "utopia-php/console": "0.2.*", "utopia-php/di": "0.3.*", "utopia-php/dsn": "0.2.*", "utopia-php/framework": "0.34.*", - "utopia-php/orchestration": "0.19.*", + "utopia-php/orchestration": "dev-chore/console-0-2-structured-commands", "utopia-php/storage": "0.18.*", "utopia-php/system": "0.10.*" }, diff --git a/composer.lock b/composer.lock index 9c0b63c..d6ce5b9 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "373b6b4aa43c1b836e3aec0c3ada4d7f", + "content-hash": "c560c276db19831862941781892c98b6", "packages": [ { "name": "brick/math", @@ -1966,20 +1966,21 @@ }, { "name": "utopia-php/console", - "version": "0.0.1", + "version": "0.2.1", "source": { "type": "git", "url": "https://github.com/utopia-php/console.git", - "reference": "f77104e4a888fa9cb3e08f32955ec09479ab7a92" + "reference": "97e3de44424ee9ea207c3129dfcc82f8df37c5b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/console/zipball/f77104e4a888fa9cb3e08f32955ec09479ab7a92", - "reference": "f77104e4a888fa9cb3e08f32955ec09479ab7a92", + "url": "https://api.github.com/repos/utopia-php/console/zipball/97e3de44424ee9ea207c3129dfcc82f8df37c5b5", + "reference": "97e3de44424ee9ea207c3129dfcc82f8df37c5b5", "shasum": "" }, "require": { - "php": ">=7.4" + "php": ">=8.0", + "utopia-php/validators": "^0.2.0" }, "require-dev": { "laravel/pint": "1.2.*", @@ -2008,9 +2009,9 @@ ], "support": { "issues": "https://github.com/utopia-php/console/issues", - "source": "https://github.com/utopia-php/console/tree/0.0.1" + "source": "https://github.com/utopia-php/console/tree/0.2.1" }, - "time": "2025-10-20T14:41:36+00:00" + "time": "2026-04-20T10:53:53+00:00" }, { "name": "utopia-php/di", @@ -2166,21 +2167,21 @@ }, { "name": "utopia-php/orchestration", - "version": "0.19.0", + "version": "dev-chore/console-0-2-structured-commands", "source": { "type": "git", "url": "https://github.com/utopia-php/orchestration.git", - "reference": "e8d8d045a8c4997bf4c9f830e9161423f005143a" + "reference": "49a86d9b5bbea655f33f2acac6cc7b5a7aeaf5bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/orchestration/zipball/e8d8d045a8c4997bf4c9f830e9161423f005143a", - "reference": "e8d8d045a8c4997bf4c9f830e9161423f005143a", + "url": "https://api.github.com/repos/utopia-php/orchestration/zipball/49a86d9b5bbea655f33f2acac6cc7b5a7aeaf5bb", + "reference": "49a86d9b5bbea655f33f2acac6cc7b5a7aeaf5bb", "shasum": "" }, "require": { "php": ">=8.0", - "utopia-php/console": "0.0.*" + "utopia-php/console": "0.2.*" }, "require-dev": { "laravel/pint": "^1.2", @@ -2210,9 +2211,9 @@ ], "support": { "issues": "https://github.com/utopia-php/orchestration/issues", - "source": "https://github.com/utopia-php/orchestration/tree/0.19.0" + "source": "https://github.com/utopia-php/orchestration/tree/chore/console-0-2-structured-commands" }, - "time": "2025-10-21T08:54:03+00:00" + "time": "2026-04-20T11:36:56+00:00" }, { "name": "utopia-php/servers", @@ -2480,29 +2481,30 @@ "packages-dev": [ { "name": "doctrine/instantiator", - "version": "2.1.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/23da848e1a2308728fe5fdddabf4be17ff9720c7", - "reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^8.4" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^14", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^10.5.58" + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -2529,7 +2531,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.1.0" + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -2545,7 +2547,7 @@ "type": "tidelift" } ], - "time": "2026-01-05T06:47:08+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "laravel/pint", @@ -4529,7 +4531,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": { + "utopia-php/orchestration": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Executor/Runner/Docker.php b/src/Executor/Runner/Docker.php index a533624..503c26c 100644 --- a/src/Executor/Runner/Docker.php +++ b/src/Executor/Runner/Docker.php @@ -9,6 +9,7 @@ use OpenRuntimes\Executor\Validator\TCP; use Swoole\Timer; use Throwable; +use Utopia\Command; use Utopia\Console; use Utopia\Http\Response; use Utopia\Logger\Log; @@ -44,7 +45,16 @@ public function getLogs(string $runtimeId, int $timeout, Response $response): vo // Wait for runtime for ($i = 0; $i < 10; $i++) { $output = ''; - $code = Console::execute('docker container inspect ' . \escapeshellarg($runtimeName), '', $output); + $error = ''; + $code = Console::execute( + (new Command('docker')) + ->argument('container') + ->argument('inspect') + ->argument($runtimeName), + '', + $output, + $error + ); if ($code === 0) { break; } @@ -147,34 +157,44 @@ public function getLogs(string $runtimeId, int $timeout, Response $response): vo $datetime = new \DateTime("now", new \DateTimeZone("UTC")); // Date used for tracking absolute log timing $output = ''; // Unused, just a refference for stdout - Console::execute('tail -F ' . $tmpLogging . '/timings.txt', '', $output, $timeout, function (string $timingChunk, mixed $process) use ($tmpLogging, &$logsChunk, &$logsProcess, &$datetime, &$offset, $introOffset): void { - $logsProcess = $process; + $error = ''; + Console::execute( + (new Command('tail')) + ->flag('-F') + ->argument($tmpLogging . '/timings.txt'), + '', + $output, + $error, + $timeout, + function (string $timingChunk, mixed $process) use ($tmpLogging, &$logsChunk, &$logsProcess, &$datetime, &$offset, $introOffset): void { + $logsProcess = $process; + + $logsPath = $tmpLogging . '/logs.txt'; + if (!\file_exists($logsPath)) { + if (!empty($logsProcess)) { + \proc_terminate($logsProcess, 9); + } - $logsPath = $tmpLogging . '/logs.txt'; - if (!\file_exists($logsPath)) { - if (!empty($logsProcess)) { - \proc_terminate($logsProcess, 9); + return; } - return; - } - - $parts = Logs::parseTiming($timingChunk, $datetime); + $parts = Logs::parseTiming($timingChunk, $datetime); - foreach ($parts as $part) { - $timestamp = $part['timestamp'] ?? ''; - $length = \intval($part['length'] ?? '0'); + foreach ($parts as $part) { + $timestamp = $part['timestamp'] ?? ''; + $length = \intval($part['length'] ?? '0'); - $logContent = \file_get_contents($tmpLogging . '/logs.txt', false, null, $introOffset + $offset, \abs($length)) ?: ''; + $logContent = \file_get_contents($tmpLogging . '/logs.txt', false, null, $introOffset + $offset, \abs($length)) ?: ''; - $logContent = \str_replace("\n", "\\n", $logContent); + $logContent = \str_replace("\n", "\\n", $logContent); - $output = $timestamp . " " . $logContent . "\n"; + $output = $timestamp . " " . $logContent . "\n"; - $logsChunk .= $output; - $offset += $length; + $logsChunk .= $output; + $offset += $length; + } } - }); + ); if (!$timerId) { throw new \Exception('Failed to create timer'); diff --git a/tests/e2e/ExecutorTest.php b/tests/e2e/ExecutorTest.php index b53a97c..cdbca4d 100644 --- a/tests/e2e/ExecutorTest.php +++ b/tests/e2e/ExecutorTest.php @@ -6,6 +6,7 @@ use PHPUnit\Framework\TestCase; use Swoole\Coroutine as Co; +use Utopia\Command; use Utopia\Console; // TODO: @Meldiron Write more tests (validators mainly) @@ -35,6 +36,50 @@ protected function setUp(): void $this->client->setKey($this->key); } + private function createTarArchive(string $directory, string $archive = 'code.tar.gz'): void + { + $stdout = ''; + $stderr = ''; + + $exitCode = Console::execute( + Command::and( + (new Command('cd'))->argument($directory), + (new Command('tar')) + ->option('--exclude', $archive) + ->flag('-czf') + ->argument($archive) + ->argument('.') + ), + '', + $stdout, + $stderr + ); + + $this->assertSame(0, $exitCode, $stderr); + } + + private function createZipArchive(string $directory, string $archive = 'code.zip'): void + { + $stdout = ''; + $stderr = ''; + + $exitCode = Console::execute( + Command::and( + (new Command('cd'))->argument($directory), + (new Command('zip')) + ->option('-x', $archive) + ->flag('-r') + ->argument($archive) + ->argument('.') + ), + '', + $stdout, + $stderr + ); + + $this->assertSame(0, $exitCode, $stderr); + } + public function testLogStream(): void { $runtimeChunks = []; @@ -44,8 +89,7 @@ public function testLogStream(): void Co\run(function () use (&$runtimeChunks, &$streamChunks, $runtimeId): void { /** Prepare build */ - $output = ''; - Console::execute('cd /app/tests/resources/functions/node && tar --exclude code.tar.gz -czf code.tar.gz .', '', $output); + $this->createTarArchive('/app/tests/resources/functions/node'); Co::join([ /** Watch logs */ @@ -201,8 +245,7 @@ public function testGetRuntimesUnauthorized(): void public function testBuild(): void { - $output = ''; - Console::execute('cd /app/tests/resources/functions/php && tar --exclude code.tar.gz -czf code.tar.gz .', '', $output); + $this->createTarArchive('/app/tests/resources/functions/php'); $runtimeId = \bin2hex(\random_bytes(4)); @@ -265,8 +308,7 @@ public function testBuild(): void public function testBuildOutputDirectory(): void { - $output = ''; - Console::execute('cd /app/tests/resources/functions/static && tar --exclude code.tar.gz -czf code.tar.gz .', '', $output); + $this->createTarArchive('/app/tests/resources/functions/static'); /** Build runtime */ $params = [ @@ -316,8 +358,7 @@ public function testBuildOutputDirectory(): void public function testBuildUncompressed(): void { - $output = ''; - Console::execute('cd /app/tests/resources/functions/node && tar --exclude code.tar.gz -czf code.tar.gz .', '', $output); + $this->createTarArchive('/app/tests/resources/functions/node'); $runtimeId = \bin2hex(\random_bytes(4)); @@ -360,8 +401,7 @@ public function testBuildUncompressed(): void public function testExecute(): void { /** Prepare function */ - $output = ''; - Console::execute('cd /app/tests/resources/functions/php && tar --exclude code.tar.gz -czf code.tar.gz .', '', $output); + $this->createTarArchive('/app/tests/resources/functions/php'); $params = [ 'runtimeId' => 'test-build', @@ -550,8 +590,7 @@ public function testExecute(): void public function testSSRLogs(): void { /** Prepare function */ - $output = ''; - Console::execute('cd /app/tests/resources/sites/astro && tar --exclude code.tar.gz -czf code.tar.gz .', '', $output); + $this->createTarArchive('/app/tests/resources/sites/astro'); $params = [ 'runtimeId' => 'test-ssr-build', @@ -610,8 +649,7 @@ public function testSSRLogs(): void public function testRestartPolicy(): void { - $output = ''; - Console::execute('cd /app/tests/resources/functions/php-exit && tar --exclude code.tar.gz -czf code.tar.gz .', '', $output); + $this->createTarArchive('/app/tests/resources/functions/php-exit'); $command = 'php src/server.php'; @@ -669,8 +707,7 @@ public function testBuildLogLimit(): void { $size128Kb = 1024 * 128; - $output = ''; - Console::execute('cd /app/tests/resources/functions/php-build-logs && tar --exclude code.tar.gz -czf code.tar.gz .', '', $output); + $this->createTarArchive('/app/tests/resources/functions/php-build-logs'); /** Build runtime */ $params = [ @@ -690,8 +727,7 @@ public function testBuildLogLimit(): void $this->assertStringContainsString('First log', (string) $response['body']['message']); $this->assertStringContainsString('Last log', (string) $response['body']['message']); - $output = ''; - Console::execute('cd /app/tests/resources/functions/php-build-logs && tar --exclude code.tar.gz -czf code.tar.gz .', '', $output); + $this->createTarArchive('/app/tests/resources/functions/php-build-logs'); /** Build runtime */ $params = [ @@ -734,8 +770,7 @@ public function testBuildLogLimit(): void $this->assertStringNotContainsString('Last log', (string) $response['body']['message']); $this->assertStringContainsString('First log', (string) $response['body']['message']); - $output = ''; - Console::execute('cd /app/tests/resources/functions/php-build-logs && tar --exclude code.tar.gz -czf code.tar.gz .', '', $output); + $this->createTarArchive('/app/tests/resources/functions/php-build-logs'); /** Build runtime */ $params = [ @@ -1016,8 +1051,7 @@ public function provideScenarios(): \Iterator public function testScenarios(string $image, string $entrypoint, string $folder, string $version, string $startCommand, string $buildCommand, callable $assertions, ?callable $body = null, bool $logging = true, string $mimeType = "application/json", float $cpus = 1, int $memory = 512, ?callable $buildAssertions = null): void { /** Prepare deployment */ - $output = ''; - Console::execute(sprintf('cd /app/tests/resources/functions/%s && tar --exclude code.tar.gz -czf code.tar.gz .', $folder), '', $output); + $this->createTarArchive(sprintf('/app/tests/resources/functions/%s', $folder)); $runtimeId = \bin2hex(\random_bytes(4)); @@ -1101,8 +1135,7 @@ public function provideCustomRuntimes(): \Iterator public function testCustomRuntimes(string $folder, string $image, string $entrypoint, string $buildCommand): void { // Prepare tar.gz files - $output = ''; - Console::execute(sprintf('cd /app/tests/resources/functions/%s && tar --exclude code.tar.gz -czf code.tar.gz .', $folder), '', $output); + $this->createTarArchive(sprintf('/app/tests/resources/functions/%s', $folder)); $runtimeId = \bin2hex(\random_bytes(4)); @@ -1171,8 +1204,7 @@ public function testCustomRuntimes(string $folder, string $image, string $entryp public function testZipBuild(): void { /** Prepare function */ - $output = ''; - Console::execute('cd /app/tests/resources/functions/php && zip -x code.zip -r code.zip .', '', $output); + $this->createZipArchive('/app/tests/resources/functions/php'); $runtimeId = \bin2hex(\random_bytes(4)); @@ -1259,8 +1291,7 @@ public function testCommands(): void public function testLogStreamPersistent(): void { - $output = ''; - Console::execute('cd /app/tests/resources/functions/node && tar --exclude code.tar.gz -czf code.tar.gz .', '', $output); + $this->createTarArchive('/app/tests/resources/functions/node'); $runtimeEnd = 0; $realtimeEnd = 0;