remoteRunner = new class implements RemoteCommandRunner { /** @var array */ public array $scripts = []; public function run(Server $server, string $script): string { $this->scripts[] = $script; return str_contains($script, 'docker manifest inspect') ? "image_digest=sha256:registrydigest\n" : "image_digest=billing-api:aaaaaaaaaaaa@sha256:localdigest\n"; } }; app()->instance(RemoteCommandRunner::class, $this->remoteRunner); }); it('builds a target-server artifact over ssh with a temporary deploy key and stores the resolved digest', function () { $organisation = Organisation::factory()->create(); $server = buildServerFor($organisation); $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]); $artifact = app(PlanBuildArtifact::class)->execute($environment, str_repeat('a', 40)); $built = app(BuildApplicationArtifact::class)->execute($artifact); expect($built->status)->toBe(BuildArtifactStatus::AVAILABLE) ->and($built->image_digest)->toBe('sha256:localdigest') ->and($this->remoteRunner->scripts[0])->toContain('GIT_SSH_COMMAND') ->and($this->remoteRunner->scripts[0])->toContain('git clone --depth 1 --branch') ->and($this->remoteRunner->scripts[0])->toContain('docker build --file Dockerfile.keystone') ->and($this->remoteRunner->scripts[0])->toContain('/home/keystone/operations/build-') ->and($this->remoteRunner->scripts[0])->toContain('trap cleanup EXIT'); }); it('resolves external registry artifacts without building locally', function () { $organisation = Organisation::factory()->create(); $server = buildServerFor($organisation); $organisation->registries()->create([ 'name' => 'GHCR', 'type' => RegistryType::GHCR, 'url' => 'ghcr.io/example', ]); $application = Application::factory()->for($organisation)->create([ 'name' => 'Billing API', 'repository_url' => 'git@example.com:org/repo.git', ]); $environment = app(CreateLaravelEnvironment::class)->execute($application->refresh(), 'production'); $environment->services()->first()->update(['server_id' => $server->id]); $environment->services()->first()->update(['desired_replicas' => 2]); $artifact = app(PlanBuildArtifact::class)->execute($environment, str_repeat('b', 40)); $built = app(BuildApplicationArtifact::class)->execute($artifact); 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('ghcr.io/example/billing-api:bbbbbbbbbbbb') ->and($this->remoteRunner->scripts[0])->not->toContain('docker build') ->and($this->remoteRunner->scripts[0])->not->toContain('git clone'); }); function buildServerFor(Organisation $organisation): Server { $provider = Provider::factory()->forOrganisation($organisation)->create(); $network = Network::create([ 'organisation_id' => $organisation->id, 'provider_id' => $provider->id, 'name' => 'test-network', 'ip_range' => '10.0.0.0/24', ]); return Server::factory() ->forOrganisation($organisation->id) ->forProvider($provider->id) ->forNetwork($network->id) ->create(); }