Implement Keystone environment deployments

This commit is contained in:
2026-05-13 16:11:23 +01:00
parent 65d3142d03
commit aa680b25fd
175 changed files with 10258 additions and 740 deletions

View File

@@ -0,0 +1,174 @@
<?php
use App\Enums\DeployPolicy;
use App\Enums\EnvironmentVariableSource;
use App\Enums\SchedulerMode;
use App\Enums\ServiceCategory;
use App\Enums\ServiceType;
use App\Models\Environment;
use App\Models\Service;
use App\Services\Compose\ComposeRenderer;
it('renders docker compose for managed postgres services', function () {
$service = Service::factory()->create([
'name' => 'primary-postgres',
'category' => ServiceCategory::DATABASE,
'type' => ServiceType::POSTGRES,
'version' => '18',
'version_track' => '18',
'driver_name' => 'postgres.18',
'deploy_policy' => DeployPolicy::DEPENDENCY_ONLY,
'credentials' => [
'user' => 'keystone',
'password' => 'secret',
'db' => 'keystone',
],
'default_cpu_limit' => 1.5,
'default_memory_limit_mb' => 1024,
]);
$compose = app(ComposeRenderer::class)->render($service);
expect($compose)
->toContain('services:')
->toContain('primary_postgres:')
->toContain('image: "postgres:18"')
->toContain('cpus: 1.500')
->toContain('mem_limit: "1024m"')
->toContain("keystone_service_{$service->id}_postgres_data:");
});
it('renders compose for caddy gateway with public ports and named volumes', function () {
$service = Service::factory()->create([
'name' => 'gateway',
'category' => ServiceCategory::GATEWAY,
'type' => ServiceType::CADDY,
'version' => '2',
'version_track' => '2',
'driver_name' => 'caddy.2',
]);
$compose = app(ComposeRenderer::class)->render($service);
expect($compose)
->toContain('image: "caddy:2"')
->toContain('- "80:80"')
->toContain('- "443:443"')
->toContain("keystone_service_{$service->id}_caddy_data:");
});
it('renders laravel scheduler and worker runtime roles into compose', function () {
$environment = Environment::factory()->create([
'scheduler_enabled' => true,
'scheduler_mode' => SchedulerMode::SINGLE,
]);
$web = Service::factory()->for($environment)->create([
'name' => 'web',
'category' => ServiceCategory::APPLICATION,
'type' => ServiceType::LARAVEL,
'version' => 'php-8.4',
'version_track' => 'php-8.4',
'driver_name' => 'laravel.php-8.4',
'process_roles' => ['web', 'scheduler'],
'desired_replicas' => 1,
]);
$environment->forceFill(['scheduler_target_service_id' => $web->id])->save();
$worker = Service::factory()->for($environment)->create([
'name' => 'worker',
'category' => ServiceCategory::APPLICATION,
'type' => ServiceType::LARAVEL,
'version' => 'php-8.4',
'version_track' => 'php-8.4',
'driver_name' => 'laravel.php-8.4',
'process_roles' => ['worker'],
'config' => [
'command' => 'php artisan queue:work --sleep=3 --tries=3',
],
]);
expect(app(ComposeRenderer::class)->render($web))
->toContain('AUTORUN_LARAVEL_SCHEDULER: "true"')
->toContain('healthcheck:')
->and(app(ComposeRenderer::class)->render($worker))
->toContain('command: "php artisan queue:work --sleep=3 --tries=3"')
->not->toContain('healthcheck:');
});
it('enforces scheduler mode when rendering laravel runtime env', function () {
$environment = Environment::factory()->create([
'scheduler_enabled' => true,
'scheduler_mode' => SchedulerMode::SINGLE,
]);
$target = Service::factory()->for($environment)->create([
'name' => 'web',
'category' => ServiceCategory::APPLICATION,
'type' => ServiceType::LARAVEL,
'version' => 'php-8.4',
'version_track' => 'php-8.4',
'driver_name' => 'laravel.php-8.4',
'process_roles' => ['web', 'scheduler'],
'desired_replicas' => 1,
]);
$nonTarget = Service::factory()->for($environment)->create([
'name' => 'worker',
'category' => ServiceCategory::APPLICATION,
'type' => ServiceType::LARAVEL,
'version' => 'php-8.4',
'version_track' => 'php-8.4',
'driver_name' => 'laravel.php-8.4',
'process_roles' => ['worker', 'scheduler'],
'desired_replicas' => 1,
]);
$environment->forceFill(['scheduler_target_service_id' => $target->id])->save();
expect(app(ComposeRenderer::class)->render($target->refresh()))
->toContain('AUTORUN_LARAVEL_SCHEDULER: "true"')
->and(app(ComposeRenderer::class)->render($nonTarget->refresh()))
->not->toContain('AUTORUN_LARAVEL_SCHEDULER');
$environment->forceFill([
'scheduler_mode' => SchedulerMode::EVERY_REPLICA,
'scheduler_target_service_id' => null,
])->save();
expect(app(ComposeRenderer::class)->render($target->refresh()))
->toContain('AUTORUN_LARAVEL_SCHEDULER: "true"')
->and(app(ComposeRenderer::class)->render($nonTarget->refresh()))
->toContain('AUTORUN_LARAVEL_SCHEDULER: "true"');
});
it('renders environment variables into laravel runtime compose', function () {
$environment = Environment::factory()->create(['name' => 'production']);
$environment->variables()->create([
'key' => 'DB_CONNECTION',
'value' => 'pgsql',
'source' => EnvironmentVariableSource::MANAGED_ATTACHMENT,
'overridable' => false,
]);
$environment->variables()->create([
'key' => 'FEATURE_FLAG',
'value' => 'enabled',
'source' => EnvironmentVariableSource::USER,
'overridable' => true,
]);
$service = Service::factory()->for($environment)->create([
'name' => 'web',
'category' => ServiceCategory::APPLICATION,
'type' => ServiceType::LARAVEL,
'version' => 'php-8.4',
'version_track' => 'php-8.4',
'driver_name' => 'laravel.php-8.4',
'process_roles' => ['web'],
]);
expect(app(ComposeRenderer::class)->render($service))
->toContain('DB_CONNECTION: "pgsql"')
->toContain('FEATURE_FLAG: "enabled"')
->toContain('APP_ENV: "production"');
expect(app(ComposeRenderer::class)->renderEnvironmentFile($service))
->toContain('DB_CONNECTION=pgsql')
->toContain('FEATURE_FLAG=enabled')
->toContain('APP_ENV=production');
});