Add managed registry provisioning, pruning, and readiness tracking

This commit is contained in:
2026-06-08 20:44:16 +01:00
parent 5b977c1f41
commit 3a851db08f
52 changed files with 2706 additions and 116 deletions

View File

@@ -5,6 +5,7 @@ use App\Actions\Applications\GenerateDeployKey;
use App\Actions\Environments\BuildApplicationArtifact;
use App\Actions\Environments\PlanBuildArtifact;
use App\Enums\BuildArtifactStatus;
use App\Enums\BuildStrategy;
use App\Enums\RegistryType;
use App\Models\Application;
use App\Models\Network;
@@ -23,7 +24,7 @@ beforeEach(function () {
{
$this->scripts[] = $script;
return str_contains($script, 'docker manifest inspect')
return str_contains($script, 'docker buildx imagetools inspect') || str_contains($script, 'push_output=$(docker push')
? "image_digest=sha256:registrydigest\n"
: "image_digest=billing-api:aaaaaaaaaaaa@sha256:localdigest\n";
}
@@ -80,13 +81,64 @@ it('resolves external registry artifacts without building locally', function ()
expect($built->registry_ref)->toBe('ghcr.io/example/billing-api:bbbbbbbbbbbb')
->and($built->image_digest)->toBe('sha256:registrydigest')
->and($this->remoteRunner->scripts[0])->toContain('docker manifest inspect')
->and($this->remoteRunner->scripts[0])->toContain('docker buildx imagetools inspect')
->and($this->remoteRunner->scripts[0])->toContain('ghcr.io/example/billing-api:bbbbbbbbbbbb')
->and($this->remoteRunner->scripts[0])->not->toContain('docker build')
->and($this->remoteRunner->scripts[0])->not->toContain('docker build --file')
->and($this->remoteRunner->scripts[0])->not->toContain('git clone');
});
function buildServerFor(Organisation $organisation): Server
it('builds and pushes managed registry artifacts without embedding registry credentials', function () {
$organisation = Organisation::factory()->create();
$server = buildServerFor($organisation, true);
$organisation->registries()->create([
'name' => 'Managed',
'type' => RegistryType::MANAGED,
'url' => 'registry.example.com',
'credentials' => [
'build_username' => 'keystone-build',
'build_password' => 'super-secret-password',
'runtime_username' => 'keystone-runtime',
'runtime_password' => 'runtime-secret',
],
'control_server_id' => $server->id,
'health_status' => 'healthy',
'readiness_checks' => ['control_https' => 'passed', 'build_push' => 'passed'],
'ready_at' => now(),
]);
$application = Application::factory()->for($organisation)->create([
'name' => 'Billing API',
'repository_url' => 'git@example.com:org/repo.git',
]);
app(GenerateDeployKey::class)->execute($application, [
'public' => 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAITestPublicKey keystone',
'private' => "-----BEGIN OPENSSH PRIVATE KEY-----\ntest\n-----END OPENSSH PRIVATE KEY-----",
'fingerprint' => 'SHA256:test',
]);
$environment = app(CreateLaravelEnvironment::class)->execute($application->refresh(), 'production');
$environment->services()->first()->update([
'server_id' => $server->id,
'desired_replicas' => 2,
]);
$artifact = app(PlanBuildArtifact::class)->execute($environment, str_repeat('c', 40));
$built = app(BuildApplicationArtifact::class)->execute($artifact);
expect($built->registry_ref)->toBe("registry.example.com/keystone/{$application->uuid}/{$environment->uuid}:cccccccccccc")
->and($built->metadata['build_strategy'])->toBe(BuildStrategy::DEDICATED_BUILDER->value)
->and($built->image_digest)->toBe('sha256:registrydigest')
->and($this->remoteRunner->scripts[0])->toContain('flock 9')
->and($this->remoteRunner->scripts[0])->toContain('docker login')
->and($this->remoteRunner->scripts[0])->toContain(base64_encode('super-secret-password'))
->and($this->remoteRunner->scripts[0])->toContain('docker build --file Dockerfile.keystone')
->and($this->remoteRunner->scripts[0])->toContain('push_output=$(docker push')
->and($this->remoteRunner->scripts[0])->toContain('digest: \\(sha256:')
->and($this->remoteRunner->scripts[0])->not->toContain('docker manifest inspect')
->and($this->remoteRunner->scripts[0])->not->toContain('"digest"')
->and($this->remoteRunner->scripts[0])->not->toContain('super-secret-password')
->and($this->remoteRunner->scripts[0])->toContain('DOCKER_CONFIG=\'/root/.docker\'');
});
function buildServerFor(Organisation $organisation, bool $buildEnabled = false): Server
{
$provider = Provider::factory()->forOrganisation($organisation)->create();
$network = Network::create([
@@ -100,5 +152,8 @@ function buildServerFor(Organisation $organisation): Server
->forOrganisation($organisation->id)
->forProvider($provider->id)
->forNetwork($network->id)
->create();
->create([
'is_control_node' => $buildEnabled,
'build_enabled' => $buildEnabled,
]);
}