Files
coolify/app/Actions/Application/StopApplication.php
T
Andras Bacsai f21acdc3e4 fix(notifications): notify when a resource stops unexpectedly
Status checks now queue a delayed check for each application, preview,
service part, or database that goes from up to exited. After 2 minutes
it sends the "Resource status changes" notification if the resource is
still stopped, no deployment runs, and Coolify did not stop it. Each
resource sends at most one notification in 15 minutes.

Closes #9493
2026-11-03 20:18:50 +01:00

103 lines
3.7 KiB
PHP

<?php
namespace App\Actions\Application;
use App\Actions\Server\CleanupDocker;
use App\Events\ApplicationStopFailed;
use App\Events\ServiceStatusChanged;
use App\Jobs\NotifyResourceStoppedJob;
use App\Models\Application;
use Illuminate\Support\Facades\Log;
use Lorisleiva\Actions\Concerns\AsAction;
class StopApplication
{
use AsAction;
public string $jobQueue = 'high';
public function handle(Application $application, bool $previewDeployments = false, bool $dockerCleanup = true, bool $resetRestartCount = true, bool $removeContainers = true): ?string
{
NotifyResourceStoppedJob::expectStop($application, $application->settings->stopGracePeriodSeconds());
$containerPresent = ! $removeContainers;
$servers = collect([$application->destination->server]);
if ($application?->additional_servers?->count() > 0) {
$servers = $servers->merge($application->additional_servers);
}
$errors = [];
foreach ($servers as $server) {
try {
if (! $server->isFunctional()) {
$errors[] = "Server {$server->name} is not functional.";
continue;
}
if ($server->isSwarm()) {
$containerPresent = false;
instant_remote_process(['docker stack rm '.escapeshellarg($application->uuid)], $server);
continue;
}
$containers = $previewDeployments
? getCurrentApplicationContainerStatus($server, $application, includePullrequests: true)
: getCurrentApplicationContainerStatus($server, $application, 0);
$containersToStop = $containers->pluck('Names')->toArray();
$timeout = $application->settings->stopGracePeriodSeconds();
foreach ($containersToStop as $containerName) {
$escapedContainerName = escapeshellarg($containerName);
$commands = [dockerStopCommand($timeout, $escapedContainerName, $server)];
if ($removeContainers) {
$commands[] = "docker rm -f {$escapedContainerName}";
}
try {
instant_remote_process(command: $commands, server: $server);
} catch (\Exception $e) {
$errors[] = $e->getMessage();
}
}
if ($removeContainers && $application->build_pack === 'dockercompose') {
$application->deleteConnectedNetworks();
}
if ($dockerCleanup) {
CleanupDocker::dispatchAfterStop($server);
}
} catch (\Exception $e) {
$errors[] = $e->getMessage();
}
}
$teamId = $application->environment->project->team->id;
if ($errors !== []) {
$message = implode(' ', $errors);
Log::warning('Failed to stop application', ['application_uuid' => $application->uuid, 'error' => $message]);
ApplicationStopFailed::dispatch($teamId, $application->uuid, $message);
return $message;
}
$status = [
'status' => 'exited',
'container_present' => $containerPresent,
];
if ($resetRestartCount) {
$status = array_merge($status, [
'restart_count' => 0,
'last_restart_at' => null,
'last_restart_type' => null,
'restart_limit_reached' => false,
]);
}
$application->update($status);
ServiceStatusChanged::dispatch($teamId);
return null;
}
}