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,115 @@
<?php
use App\Enums\OperationKind;
use App\Enums\OperationStatus;
use App\Enums\SchedulerMode;
use App\Models\Application;
use App\Models\BuildArtifact;
use App\Models\Environment;
use App\Models\Operation;
use App\Models\Organisation;
use App\Models\Provider;
use App\Models\Server;
use App\Models\Service;
use App\Models\ServiceReplica;
use App\Models\ServiceSlice;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
it('creates an environment as the primary application deployment unit', function () {
$user = User::factory()->create();
$organisation = Organisation::factory()->create(['owner_id' => $user->id]);
$application = Application::factory()->for($organisation)->create();
$environment = Environment::factory()->for($application)->create();
expect($environment)
->toBeInstanceOf(Environment::class)
->and($environment->scheduler_enabled)->toBeTrue()
->and($environment->scheduler_mode)->toBe(SchedulerMode::SINGLE);
});
it('records operations and operation steps for service deployments', function () {
$service = Service::factory()->create();
$operation = Operation::factory()->make([
'kind' => OperationKind::SERVICE_DEPLOY,
'status' => OperationStatus::PENDING,
]);
$service->operations()->save($operation);
$operation->steps()->create([
'name' => 'Render Compose file',
'order' => 1,
'status' => OperationStatus::PENDING,
'script' => 'docker compose config',
]);
expect($operation)
->toBeInstanceOf(Operation::class)
->and($operation->hash)->not->toBeEmpty()
->and($operation->steps)->toHaveCount(1)
->and($operation->steps->first()->operation->is($operation))->toBeTrue();
});
it('models replicas slices attachments variables and build artifacts', function () {
$user = User::factory()->create();
$organisation = Organisation::factory()->create(['owner_id' => $user->id]);
$application = Application::factory()->for($organisation)->create();
$environment = Environment::factory()->for($application)->create();
$service = Service::factory()->for($environment)->create([
'organisation_id' => $organisation->id,
]);
$provider = Provider::factory()->forOrganisation($organisation)->create();
$network = $organisation->networks()->create([
'name' => 'keystone',
'external_id' => 'net-12345',
'provider_id' => $provider->id,
'ip_range' => fake()->ipv4().'/24',
]);
$server = Server::factory()->create([
'organisation_id' => $organisation->id,
'provider_id' => $provider->id,
'network_id' => $network->id,
]);
$replica = $service->replicas()->create([
'server_id' => $server->id,
'container_name' => 'postgres-1',
'image_digest' => 'sha256:postgres',
'internal_host' => 'postgres-1',
'internal_port' => 5432,
'status' => 'running',
]);
$slice = ServiceSlice::factory()->for($service)->create([
'environment_id' => $environment->id,
'name' => 'production',
'credentials' => ['username' => 'app', 'password' => 'secret', 'database' => 'app'],
]);
$attachment = $environment->attachments()->create([
'service_id' => $service->id,
'service_slice_id' => $slice->id,
'role' => 'database',
'is_primary' => true,
]);
$variable = $environment->variables()->create([
'key' => 'DB_PASSWORD',
'value' => 'secret',
'source' => 'managed_attachment',
'service_slice_id' => $slice->id,
'overridable' => false,
]);
$artifact = $environment->buildArtifacts()->create([
'commit_sha' => str_repeat('a', 40),
'image_tag' => 'app:abc123',
'image_digest' => 'sha256:abc123',
'status' => 'available',
]);
expect($service->slices->first())->toBeInstanceOf(ServiceSlice::class)
->and($replica)->toBeInstanceOf(ServiceReplica::class)
->and($attachment->serviceSlice->is($slice))->toBeTrue()
->and($variable->value)->toBe('secret')
->and($artifact)->toBeInstanceOf(BuildArtifact::class);
});