feat(DeploymentException): add custom exception for deployment errors and update handler to exclude from reporting
This commit is contained in:
parent
0d14bc1df7
commit
133d6a0349
4 changed files with 112 additions and 8 deletions
32
app/Exceptions/DeploymentException.php
Normal file
32
app/Exceptions/DeploymentException.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Exception for expected deployment failures caused by user/application errors.
|
||||
* These are not Coolify bugs and should not be logged to laravel.log.
|
||||
* Examples: Nixpacks detection failures, missing Dockerfiles, invalid configs, etc.
|
||||
*/
|
||||
class DeploymentException extends Exception
|
||||
{
|
||||
/**
|
||||
* Create a new deployment exception instance.
|
||||
*
|
||||
* @param string $message
|
||||
* @param int $code
|
||||
*/
|
||||
public function __construct($message = '', $code = 0, ?\Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from another exception, preserving its message and stack trace.
|
||||
*/
|
||||
public static function fromException(\Throwable $exception): static
|
||||
{
|
||||
return new static($exception->getMessage(), $exception->getCode(), $exception);
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ class Handler extends ExceptionHandler
|
|||
protected $dontReport = [
|
||||
ProcessException::class,
|
||||
NonReportableException::class,
|
||||
DeploymentException::class,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
use App\Enums\ProcessStatus;
|
||||
use App\Events\ApplicationConfigurationChanged;
|
||||
use App\Events\ServiceStatusChanged;
|
||||
use App\Exceptions\DeploymentException;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\ApplicationPreview;
|
||||
|
|
@ -31,7 +32,6 @@
|
|||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Sleep;
|
||||
use Illuminate\Support\Str;
|
||||
use RuntimeException;
|
||||
use Spatie\Url\Url;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Throwable;
|
||||
|
|
@ -976,7 +976,7 @@ private function push_to_docker_registry()
|
|||
} catch (Exception $e) {
|
||||
$this->application_deployment_queue->addLogEntry('Failed to push image to docker registry. Please check debug logs for more information.');
|
||||
if ($forceFail) {
|
||||
throw new RuntimeException($e->getMessage(), 69420);
|
||||
throw new DeploymentException($e->getMessage(), 69420);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1823,7 +1823,7 @@ private function prepare_builder_image(bool $firstTry = true)
|
|||
$env_flags = $this->generate_docker_env_flags_for_secrets();
|
||||
if ($this->use_build_server) {
|
||||
if ($this->dockerConfigFileExists === 'NOK') {
|
||||
throw new RuntimeException('Docker config file (~/.docker/config.json) not found on the build server. Please run "docker login" to login to the docker registry on the server.');
|
||||
throw new DeploymentException('Docker config file (~/.docker/config.json) not found on the build server. Please run "docker login" to login to the docker registry on the server.');
|
||||
}
|
||||
$runCommand = "docker run -d --name {$this->deployment_uuid} {$env_flags} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
||||
} else {
|
||||
|
|
@ -2089,7 +2089,7 @@ private function generate_nixpacks_confs()
|
|||
if ($this->saved_outputs->get('nixpacks_type')) {
|
||||
$this->nixpacks_type = $this->saved_outputs->get('nixpacks_type');
|
||||
if (str($this->nixpacks_type)->isEmpty()) {
|
||||
throw new RuntimeException('Nixpacks failed to detect the application type. Please check the documentation of Nixpacks: https://nixpacks.com/docs/providers');
|
||||
throw new DeploymentException('Nixpacks failed to detect the application type. Please check the documentation of Nixpacks: https://nixpacks.com/docs/providers');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3676,7 +3676,7 @@ private function run_pre_deployment_command()
|
|||
return;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException('Pre-deployment command: Could not find a valid container. Is the container name correct?');
|
||||
throw new DeploymentException('Pre-deployment command: Could not find a valid container. Is the container name correct?');
|
||||
}
|
||||
|
||||
private function run_post_deployment_command()
|
||||
|
|
@ -3712,7 +3712,7 @@ private function run_post_deployment_command()
|
|||
return;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException('Post-deployment command: Could not find a valid container. Is the container name correct?');
|
||||
throw new DeploymentException('Post-deployment command: Could not find a valid container. Is the container name correct?');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -3723,7 +3723,7 @@ private function checkForCancellation(): void
|
|||
$this->application_deployment_queue->refresh();
|
||||
if ($this->application_deployment_queue->status === ApplicationDeploymentStatus::CANCELLED_BY_USER->value) {
|
||||
$this->application_deployment_queue->addLogEntry('Deployment cancelled by user, stopping execution.');
|
||||
throw new \RuntimeException('Deployment cancelled by user', 69420);
|
||||
throw new DeploymentException('Deployment cancelled by user', 69420);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3756,7 +3756,7 @@ private function isInTerminalState(): bool
|
|||
|
||||
if ($this->application_deployment_queue->status === ApplicationDeploymentStatus::CANCELLED_BY_USER->value) {
|
||||
$this->application_deployment_queue->addLogEntry('Deployment cancelled by user, stopping execution.');
|
||||
throw new \RuntimeException('Deployment cancelled by user', 69420);
|
||||
throw new DeploymentException('Deployment cancelled by user', 69420);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
|||
71
tests/Unit/DeploymentExceptionTest.php
Normal file
71
tests/Unit/DeploymentExceptionTest.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
use App\Exceptions\DeploymentException;
|
||||
use App\Exceptions\Handler;
|
||||
|
||||
test('DeploymentException is in the dontReport array', function () {
|
||||
$handler = new Handler(app());
|
||||
|
||||
// Use reflection to access the protected $dontReport property
|
||||
$reflection = new ReflectionClass($handler);
|
||||
$property = $reflection->getProperty('dontReport');
|
||||
$property->setAccessible(true);
|
||||
$dontReport = $property->getValue($handler);
|
||||
|
||||
expect($dontReport)->toContain(DeploymentException::class);
|
||||
});
|
||||
|
||||
test('DeploymentException can be created with a message', function () {
|
||||
$exception = new DeploymentException('Test deployment error');
|
||||
|
||||
expect($exception->getMessage())->toBe('Test deployment error');
|
||||
expect($exception)->toBeInstanceOf(Exception::class);
|
||||
});
|
||||
|
||||
test('DeploymentException can be created with a message and code', function () {
|
||||
$exception = new DeploymentException('Test error', 69420);
|
||||
|
||||
expect($exception->getMessage())->toBe('Test error');
|
||||
expect($exception->getCode())->toBe(69420);
|
||||
});
|
||||
|
||||
test('DeploymentException can be created from another exception', function () {
|
||||
$originalException = new RuntimeException('Original error', 500);
|
||||
$deploymentException = DeploymentException::fromException($originalException);
|
||||
|
||||
expect($deploymentException->getMessage())->toBe('Original error');
|
||||
expect($deploymentException->getCode())->toBe(500);
|
||||
expect($deploymentException->getPrevious())->toBe($originalException);
|
||||
});
|
||||
|
||||
test('DeploymentException is not reported when thrown', function () {
|
||||
$handler = new Handler(app());
|
||||
|
||||
// DeploymentException should not be reported (logged)
|
||||
$exception = new DeploymentException('Test deployment failure');
|
||||
|
||||
// Check that the exception should not be reported
|
||||
$reflection = new ReflectionClass($handler);
|
||||
$method = $reflection->getMethod('shouldReport');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$shouldReport = $method->invoke($handler, $exception);
|
||||
|
||||
expect($shouldReport)->toBeFalse();
|
||||
});
|
||||
|
||||
test('RuntimeException is still reported when thrown', function () {
|
||||
$handler = new Handler(app());
|
||||
|
||||
// RuntimeException should still be reported (this is for Coolify bugs)
|
||||
$exception = new RuntimeException('Unexpected error in Coolify code');
|
||||
|
||||
// Check that the exception should be reported
|
||||
$reflection = new ReflectionClass($handler);
|
||||
$method = $reflection->getMethod('shouldReport');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$shouldReport = $method->invoke($handler, $exception);
|
||||
|
||||
expect($shouldReport)->toBeTrue();
|
||||
});
|
||||
Loading…
Reference in a new issue