fix(deploy): start queued deployments atomically and report cancelled multi-server runs

- Decide whether to start a queued deployment inside the application and
  server lock, so two requests cannot start the same app twice.
- Send the multi-server summary when the last child is cancelled.
- Show the registry message when an inline Dockerfile image is missing on a
  deployments-only additional server.
This commit is contained in:
Andras Bacsai
2026-09-30 18:10:10 +02:00
parent ce32bff820
commit f8f2945aec
4 changed files with 207 additions and 44 deletions
@@ -61,6 +61,62 @@ describe('queue_application_deployment commit resolution', function () {
expect($creationLevel)->toBeGreaterThan($initialLevel);
});
test('starts a deployment only while the admission transaction is open', function () {
$application = makeApplication($this->environment->id, $this->destination->id, 'HEAD');
$initialLevel = DB::transactionLevel();
$startLevel = null;
ApplicationDeploymentQueue::updated(function (ApplicationDeploymentQueue $deployment) use (&$startLevel): void {
if ($deployment->status === ApplicationDeploymentStatus::IN_PROGRESS->value) {
$startLevel = DB::transactionLevel();
}
});
$result = queue_application_deployment($application, 'atomic-start');
expect($result['status'])->toBe('queued')
->and($startLevel)->toBeGreaterThan($initialLevel);
Bus::assertDispatchedTimes(ApplicationDeploymentJob::class, 1);
});
test('starts the next queued deployment only while a transaction is open', function (string $trigger) {
$application = makeApplication($this->environment->id, $this->destination->id, 'HEAD');
$this->server->settings->update(['concurrent_builds' => 0]);
queue_application_deployment($application, 'waiting-deployment');
$this->server->settings->update(['concurrent_builds' => 1]);
$initialLevel = DB::transactionLevel();
$startLevel = null;
ApplicationDeploymentQueue::updated(function (ApplicationDeploymentQueue $deployment) use (&$startLevel): void {
if ($deployment->status === ApplicationDeploymentStatus::IN_PROGRESS->value) {
$startLevel = DB::transactionLevel();
}
});
$trigger === 'queue_next_deployment'
? queue_next_deployment($application)
: next_after_cancel($this->server);
expect($startLevel)->toBeGreaterThan($initialLevel)
->and(ApplicationDeploymentQueue::query()->sole()->status)->toBe(ApplicationDeploymentStatus::IN_PROGRESS->value);
Bus::assertDispatchedTimes(ApplicationDeploymentJob::class, 1);
})->with(['queue_next_deployment', 'next_after_cancel']);
test('does not start a queued deployment that another worker already started', function () {
$application = makeApplication($this->environment->id, $this->destination->id, 'HEAD');
$this->server->settings->update(['concurrent_builds' => 0]);
queue_application_deployment($application, 'stale-deployment');
$this->server->settings->update(['concurrent_builds' => 5]);
$stale = ApplicationDeploymentQueue::query()->sole();
ApplicationDeploymentQueue::query()->whereKey($stale->id)->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]);
expect(start_queued_deployment($stale))->toBeFalse();
Bus::assertNotDispatched(ApplicationDeploymentJob::class);
});
test('skips a matching commit without creating or dispatching a second deployment', function () {
$application = makeApplication($this->environment->id, $this->destination->id, 'HEAD');