mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(deployments): log the configured timeout when a deployment times out
This commit is contained in:
@@ -34,6 +34,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\TimeoutExceededException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Sleep;
|
||||
@@ -5953,7 +5954,9 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$errorClass = get_class($exception);
|
||||
|
||||
$this->application_deployment_queue->addLogEntry('========================================', 'stderr');
|
||||
if ($exception instanceof DeploymentException && $exception->isMessageAlreadyLogged()) {
|
||||
if ($exception instanceof TimeoutExceededException) {
|
||||
$this->application_deployment_queue->addLogEntry("Deployment failed: Timeout of {$this->timeout} seconds exceeded. Increase the deployment timeout in the Server settings (Advanced > Builds).", 'stderr');
|
||||
} elseif ($exception instanceof DeploymentException && $exception->isMessageAlreadyLogged()) {
|
||||
$this->application_deployment_queue->addLogEntry('Deployment failed.', 'stderr');
|
||||
} else {
|
||||
$this->application_deployment_queue->addLogEntry("Deployment failed: {$errorMessage}", 'stderr');
|
||||
|
||||
@@ -4,6 +4,8 @@ use App\Exceptions\DeploymentException;
|
||||
use App\Jobs\ApplicationDeploymentJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use Illuminate\Contracts\Queue\Job;
|
||||
use Illuminate\Queue\TimeoutExceededException;
|
||||
|
||||
/**
|
||||
* Test to verify that deployment errors are properly logged with comprehensive details.
|
||||
@@ -19,7 +21,7 @@ use App\Models\ApplicationDeploymentQueue;
|
||||
*/
|
||||
it('logs comprehensive error details when failed() is called', function () {
|
||||
// Create a mock exception with all properties
|
||||
$innerException = new \RuntimeException('Connection refused', 111);
|
||||
$innerException = new RuntimeException('Connection refused', 111);
|
||||
$exception = new DeploymentException(
|
||||
'Failed to start container',
|
||||
500,
|
||||
@@ -71,7 +73,7 @@ it('logs comprehensive error details when failed() is called', function () {
|
||||
$job = Mockery::mock(ApplicationDeploymentJob::class)->makePartial();
|
||||
$job->shouldAllowMockingProtectedMethods();
|
||||
|
||||
$reflection = new \ReflectionClass(ApplicationDeploymentJob::class);
|
||||
$reflection = new ReflectionClass(ApplicationDeploymentJob::class);
|
||||
|
||||
$queueProperty = $reflection->getProperty('application_deployment_queue');
|
||||
$queueProperty->setAccessible(true);
|
||||
@@ -137,7 +139,7 @@ it('logs comprehensive error details when failed() is called', function () {
|
||||
});
|
||||
|
||||
it('handles exceptions with no message gracefully', function () {
|
||||
$exception = new \Exception;
|
||||
$exception = new Exception;
|
||||
|
||||
$mockQueue = Mockery::mock(ApplicationDeploymentQueue::class);
|
||||
$logEntries = [];
|
||||
@@ -180,7 +182,7 @@ it('handles exceptions with no message gracefully', function () {
|
||||
$job = Mockery::mock(ApplicationDeploymentJob::class)->makePartial();
|
||||
$job->shouldAllowMockingProtectedMethods();
|
||||
|
||||
$reflection = new \ReflectionClass(ApplicationDeploymentJob::class);
|
||||
$reflection = new ReflectionClass(ApplicationDeploymentJob::class);
|
||||
|
||||
$queueProperty = $reflection->getProperty('application_deployment_queue');
|
||||
$queueProperty->setAccessible(true);
|
||||
@@ -215,7 +217,7 @@ it('handles exceptions with no message gracefully', function () {
|
||||
|
||||
it('wraps exceptions in deployment methods with DeploymentException', function () {
|
||||
// Verify that our deployment methods wrap exceptions properly
|
||||
$originalException = new \RuntimeException('Container not found');
|
||||
$originalException = new RuntimeException('Container not found');
|
||||
|
||||
try {
|
||||
throw new DeploymentException('Failed to start container', 0, $originalException);
|
||||
@@ -228,7 +230,7 @@ it('wraps exceptions in deployment methods with DeploymentException', function (
|
||||
|
||||
it('logs error code 0 correctly', function () {
|
||||
// Verify that error code 0 is logged (previously skipped due to falsy check)
|
||||
$exception = new \Exception('Test error', 0);
|
||||
$exception = new Exception('Test error', 0);
|
||||
|
||||
$mockQueue = Mockery::mock(ApplicationDeploymentQueue::class);
|
||||
$logEntries = [];
|
||||
@@ -271,7 +273,7 @@ it('logs error code 0 correctly', function () {
|
||||
$job = Mockery::mock(ApplicationDeploymentJob::class)->makePartial();
|
||||
$job->shouldAllowMockingProtectedMethods();
|
||||
|
||||
$reflection = new \ReflectionClass(ApplicationDeploymentJob::class);
|
||||
$reflection = new ReflectionClass(ApplicationDeploymentJob::class);
|
||||
|
||||
$queueProperty = $reflection->getProperty('application_deployment_queue');
|
||||
$queueProperty->setAccessible(true);
|
||||
@@ -305,7 +307,7 @@ it('logs error code 0 correctly', function () {
|
||||
|
||||
it('preserves original exception type in wrapped DeploymentException messages', function () {
|
||||
// Verify that when wrapping exceptions, the original exception type is included in the message
|
||||
$originalException = new \RuntimeException('Connection timeout');
|
||||
$originalException = new RuntimeException('Connection timeout');
|
||||
|
||||
// Test rolling update scenario
|
||||
$wrappedException = new DeploymentException(
|
||||
@@ -319,7 +321,7 @@ it('preserves original exception type in wrapped DeploymentException messages',
|
||||
expect($wrappedException->getPrevious())->toBe($originalException);
|
||||
|
||||
// Test health check scenario
|
||||
$healthCheckException = new \InvalidArgumentException('Invalid health check URL');
|
||||
$healthCheckException = new InvalidArgumentException('Invalid health check URL');
|
||||
$wrappedHealthCheck = new DeploymentException(
|
||||
'Health check failed ('.get_class($healthCheckException).'): '.$healthCheckException->getMessage(),
|
||||
$healthCheckException->getCode(),
|
||||
@@ -331,7 +333,7 @@ it('preserves original exception type in wrapped DeploymentException messages',
|
||||
expect($wrappedHealthCheck->getPrevious())->toBe($healthCheckException);
|
||||
|
||||
// Test docker registry push scenario
|
||||
$registryException = new \RuntimeException('Failed to authenticate');
|
||||
$registryException = new RuntimeException('Failed to authenticate');
|
||||
$wrappedRegistry = new DeploymentException(
|
||||
get_class($registryException).': '.$registryException->getMessage(),
|
||||
$registryException->getCode(),
|
||||
@@ -342,3 +344,68 @@ it('preserves original exception type in wrapped DeploymentException messages',
|
||||
expect($wrappedRegistry->getMessage())->toContain('Failed to authenticate');
|
||||
expect($wrappedRegistry->getPrevious())->toBe($registryException);
|
||||
});
|
||||
|
||||
it('logs the configured timeout when the deployment times out', function () {
|
||||
$queueJob = Mockery::mock(Job::class);
|
||||
$queueJob->shouldReceive('resolveName')->andReturn(ApplicationDeploymentJob::class);
|
||||
$exception = TimeoutExceededException::forJob($queueJob);
|
||||
|
||||
$mockQueue = Mockery::mock(ApplicationDeploymentQueue::class);
|
||||
$logEntries = [];
|
||||
|
||||
$mockQueue->shouldReceive('addLogEntry')
|
||||
->withArgs(function ($message, $type = 'stdout', $hidden = false) use (&$logEntries) {
|
||||
$logEntries[] = ['message' => $message, 'type' => $type, 'hidden' => $hidden];
|
||||
|
||||
return true;
|
||||
})
|
||||
->atLeast()->once();
|
||||
|
||||
$mockQueue->shouldReceive('update')->andReturn(true);
|
||||
|
||||
$mockApplication = Mockery::mock(Application::class);
|
||||
$mockApplication->shouldReceive('getAttribute')
|
||||
->with('build_pack')
|
||||
->andReturn('dockerfile');
|
||||
|
||||
$mockSettings = Mockery::mock();
|
||||
$mockSettings->shouldReceive('getAttribute')
|
||||
->with('is_consistent_container_name_enabled')
|
||||
->andReturn(false);
|
||||
$mockSettings->is_consistent_container_name_enabled = false;
|
||||
|
||||
$mockApplication->shouldReceive('getAttribute')
|
||||
->with('settings')
|
||||
->andReturn($mockSettings);
|
||||
|
||||
$job = Mockery::mock(ApplicationDeploymentJob::class)->makePartial();
|
||||
$job->shouldAllowMockingProtectedMethods();
|
||||
$job->timeout = 900;
|
||||
|
||||
$reflection = new ReflectionClass(ApplicationDeploymentJob::class);
|
||||
|
||||
$queueProperty = $reflection->getProperty('application_deployment_queue');
|
||||
$queueProperty->setValue($job, $mockQueue);
|
||||
|
||||
$applicationProperty = $reflection->getProperty('application');
|
||||
$applicationProperty->setValue($job, $mockApplication);
|
||||
|
||||
$pullRequestProperty = $reflection->getProperty('pull_request_id');
|
||||
$pullRequestProperty->setValue($job, 0);
|
||||
|
||||
$containerNameProperty = $reflection->getProperty('container_name');
|
||||
$containerNameProperty->setValue($job, 'test-container');
|
||||
|
||||
$job->shouldReceive('failDeployment')->andReturn();
|
||||
$job->shouldReceive('execute_remote_command')->andReturn();
|
||||
|
||||
$job->failed($exception);
|
||||
|
||||
$timeoutEntry = collect($logEntries)->first(fn ($entry) => str_contains($entry['message'], 'Timeout of 900 seconds exceeded'));
|
||||
|
||||
expect($timeoutEntry)->not->toBeNull()
|
||||
->and($timeoutEntry['message'])->toContain('Increase the deployment timeout in the Server settings')
|
||||
->and($timeoutEntry['type'])->toBe('stderr')
|
||||
->and($timeoutEntry['hidden'])->toBeFalse()
|
||||
->and(collect($logEntries)->pluck('message')->implode("\n"))->not->toContain('has timed out.');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user