From eb2a6a5c82f52d9c27d82ca7a0481fc0ac4723f6 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 5 Oct 2026 21:18:55 +0200 Subject: [PATCH] fix(deployments): fail a deployment when its job cannot be dispatched When the job constructor or the dispatch threw (for example, an invalid registry image name), the deployment stayed in_progress. It blocked later deployments of the application and used a build slot. - Mark the deployment failed with fail_undispatchable_deployment() - Throw the error again, so the UI and API still show it --- bootstrap/helpers/applications.php | 12 +++-- ...ImmediateDeploymentDispatchFailureTest.php | 53 +++++++++++++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 tests/Feature/ImmediateDeploymentDispatchFailureTest.php diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index b518abff7d..e7731e7ae0 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -116,9 +116,15 @@ function queue_application_deployment(Application $application, string $deployme } if ($admission['started']) { - ApplicationDeploymentJob::dispatch( - application_deployment_queue_id: $deployment->id, - ); + try { + ApplicationDeploymentJob::dispatch( + application_deployment_queue_id: $deployment->id, + ); + } catch (Throwable $exception) { + fail_undispatchable_deployment($deployment, $exception); + + throw $exception; + } } return [ diff --git a/tests/Feature/ImmediateDeploymentDispatchFailureTest.php b/tests/Feature/ImmediateDeploymentDispatchFailureTest.php new file mode 100644 index 0000000000..61817a87cf --- /dev/null +++ b/tests/Feature/ImmediateDeploymentDispatchFailureTest.php @@ -0,0 +1,53 @@ + InstanceSettings::firstOrCreate(['id' => 0])); + + $team = Team::factory()->create(); + $server = Server::factory()->create(['team_id' => $team->id]); + $destination = StandaloneDocker::where('server_id', $server->id)->firstOrFail(); + $project = Project::factory()->create(['team_id' => $team->id]); + $environment = $project->environments()->first() + ?? Environment::factory()->create(['project_id' => $project->id]); + $this->application = Application::factory()->create([ + 'environment_id' => $environment->id, + 'destination_id' => $destination->id, + 'destination_type' => $destination->getMorphClass(), + 'docker_registry_image_name' => 'bad name;x', + ]); + + Queue::fake(); + Notification::fake(); +}); + +test('a deployment whose job cannot be dispatched fails and does not block the next deployment', function () { + expect(fn () => queue_application_deployment($this->application, 'prr-undispatchable')) + ->toThrow(DeploymentException::class, 'Docker registry image name contains invalid characters.'); + + $failedDeployment = ApplicationDeploymentQueue::where('deployment_uuid', 'prr-undispatchable')->firstOrFail(); + expect($failedDeployment->status)->toBe('failed') + ->and($failedDeployment->logs)->toContain('Deployment could not be started'); + + $this->application->update(['docker_registry_image_name' => 'ghcr.io/coollabs/app']); + queue_application_deployment($this->application->fresh(), 'prr-next-deployment'); + + expect(ApplicationDeploymentQueue::where('deployment_uuid', 'prr-next-deployment')->value('status'))->toBe('in_progress'); + Queue::assertPushed(ApplicationDeploymentJob::class, 1); +});