diff --git a/app/Http/Livewire/Project/Application/Heading.php b/app/Http/Livewire/Project/Application/Heading.php index 0774ad441..8c0da7a1e 100644 --- a/app/Http/Livewire/Project/Application/Heading.php +++ b/app/Http/Livewire/Project/Application/Heading.php @@ -2,10 +2,8 @@ namespace App\Http\Livewire\Project\Application; -use App\Jobs\ApplicationContainerStatusJob; use App\Jobs\ContainerStatusJob; use App\Models\Application; -use App\Notifications\Application\StatusChanged; use Livewire\Component; use Visus\Cuid2\Cuid2; @@ -23,9 +21,11 @@ public function mount() public function check_status() { - ray($this->application->destination->server); dispatch_sync(new ContainerStatusJob($this->application->destination->server)); $this->application->refresh(); + $this->application->previews->each(function ($preview) { + $preview->refresh(); + }); } public function force_deploy_without_cache() diff --git a/app/Http/Livewire/Project/Application/Previews.php b/app/Http/Livewire/Project/Application/Previews.php index ad3ca5ad9..9633d03a6 100644 --- a/app/Http/Livewire/Project/Application/Previews.php +++ b/app/Http/Livewire/Project/Application/Previews.php @@ -3,6 +3,7 @@ namespace App\Http\Livewire\Project\Application; use App\Jobs\ApplicationContainerStatusJob; +use App\Jobs\ContainerStatusJob; use App\Models\Application; use App\Models\ApplicationPreview; use Illuminate\Support\Collection; @@ -23,14 +24,6 @@ public function mount() $this->parameters = get_route_parameters(); } - public function loadStatus($pull_request_id) - { - dispatch(new ApplicationContainerStatusJob( - application: $this->application, - pullRequestId: $pull_request_id - )); - } - public function load_prs() { try { diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index ddd64b089..ea682b03a 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -119,6 +119,9 @@ public function handle(): void if ($containers->count() > 0) { $this->currently_running_container_name = data_get($containers[0], 'Names'); } + if ($this->pull_request_id !== 0 && $this->pull_request_id !== null) { + $this->currently_running_container_name = $this->container_name; + } $this->application_deployment_queue->update([ 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, ]); @@ -296,7 +299,11 @@ private function deploy_pull_request() // $this->generate_build_env_variables(); // $this->add_build_env_variables_to_dockerfile(); $this->build_image(); - $this->rolling_update(); + $this->stop_running_container(); + $this->execute_remote_command( + ["echo -n 'Starting preview deployment.'"], + [$this->execute_in_builder("docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true], + ); } private function prepare_builder_image() @@ -576,10 +583,15 @@ private function generate_environment_variables($ports) private function set_labels_for_applications() { + + $appId = $this->application->id; + if ($this->pull_request_id !== 0) { + $appId = $appId . '-pr-' . $this->pull_request_id; + } $labels = []; $labels[] = 'coolify.managed=true'; $labels[] = 'coolify.version=' . config('version'); - $labels[] = 'coolify.applicationId=' . $this->application->id; + $labels[] = 'coolify.applicationId=' . $appId; $labels[] = 'coolify.type=application'; $labels[] = 'coolify.name=' . $this->application->name; if ($this->pull_request_id !== 0) { diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index f2c82f4bf..8f59c7a2c 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -3,10 +3,12 @@ namespace App\Jobs; use App\Actions\Proxy\StartProxy; +use App\Models\ApplicationPreview; use App\Models\Server; use App\Notifications\Container\ContainerRestarted; use App\Notifications\Container\ContainerStopped; use App\Notifications\Server\Unreachable; +use Arr; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldBeUnique; @@ -69,6 +71,7 @@ public function handle(): void $containers = format_docker_command_output_to_json($containers); $applications = $this->server->applications(); $databases = $this->server->databases(); + $previews = $this->server->previews(); if ($this->server->isProxyShouldRun()) { $foundProxyContainer = $containers->filter(function ($value, $key) { return data_get($value, 'Name') === '/coolify-proxy'; @@ -78,12 +81,148 @@ public function handle(): void $this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server)); } } + $foundApplications = []; + $foundApplicationPreviews = []; + $foundDatabases = []; + foreach ($containers as $container) { + $containerStatus = data_get($container, 'State.Status'); + $labels = data_get($container, 'Config.Labels'); + $labels = Arr::undot(format_docker_labels_to_json($labels)); + $labelId = data_get($labels, 'coolify.applicationId'); + ray($labelId); + if ($labelId) { + if (str_contains($labelId,'-pr-')) { + $previewId = (int) Str::after($labelId, '-pr-'); + $applicationId = (int) Str::before($labelId, '-pr-'); + $preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id',$previewId)->first(); + if ($preview) { + $foundApplicationPreviews[] = $preview->id; + $statusFromDb = $preview->status; + if ($statusFromDb !== $containerStatus) { + $preview->update(['status' => $containerStatus]); + } + } else { + //Notify user that this container should not be there. + } + } else { + $application = $applications->where('id', $labelId)->first(); + if ($application) { + $foundApplications[] = $application->id; + $statusFromDb = $application->status; + if ($statusFromDb !== $containerStatus) { + $application->update(['status' => $containerStatus]); + } + } else { + //Notify user that this container should not be there. + } + } + } else { + $uuid = data_get($labels, 'com.docker.compose.service'); + if ($uuid) { + $database = $databases->where('uuid', $uuid)->first(); + if ($database) { + $foundDatabases[] = $database->id; + $statusFromDb = $database->status; + if ($statusFromDb !== $containerStatus) { + $database->update(['status' => $containerStatus]); + } + } else { + // Notify user that this container should not be there. + } + } + } + + } + $notRunningApplications = $applications->pluck('id')->diff($foundApplications); + foreach($notRunningApplications as $applicationId) { + $application = $applications->where('id', $applicationId)->first(); + if ($application->status === 'exited') { + continue; + } + $application->update(['status' => 'exited']); + + $name = data_get($application, 'name'); + $fqdn = data_get($application, 'fqdn'); + + $containerName = $name ? "$name ($fqdn)" : $fqdn; + + $project = data_get($application, 'environment.project'); + $environment = data_get($application, 'environment'); + + $url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/application/" . $application->uuid; + + $this->server->team->notify(new ContainerStopped($containerName, $this->server, $url)); + } + $notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews); + foreach ($notRunningApplicationPreviews as $previewId) { + $preview = $previews->where('id', $previewId)->first(); + if ($preview->status === 'exited') { + continue; + } + $preview->update(['status' => 'exited']); + + $name = data_get($preview, 'name'); + $fqdn = data_get($preview, 'fqdn'); + + $containerName = $name ? "$name ($fqdn)" : $fqdn; + + $project = data_get($preview, 'application.environment.project'); + $environment = data_get($preview, 'application.environment'); + + $url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/application/" . $preview->application->uuid; + $this->server->team->notify(new ContainerStopped($containerName, $this->server, $url)); + } + $notRunningDatabases = $databases->pluck('id')->diff($foundDatabases); + foreach($notRunningDatabases as $database) { + $database = $databases->where('id', $database)->first(); + if ($database->status === 'exited') { + continue; + } + $database->update(['status' => 'exited']); + + $name = data_get($database, 'name'); + $fqdn = data_get($database, 'fqdn'); + + $containerName = $name; + + $project = data_get($database, 'environment.project'); + $environment = data_get($database, 'environment'); + + $url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/database/" . $database->uuid; + $this->server->team->notify(new ContainerStopped($containerName, $this->server, $url)); + } + + + + + + + + + + + + + return; foreach ($applications as $application) { $uuid = data_get($application, 'uuid'); - $foundContainer = $containers->filter(function ($value, $key) use ($uuid) { - return Str::startsWith(data_get($value, 'Name'), "/$uuid"); + $id = data_get($application, 'id'); + $foundContainer = $containers->filter(function ($value, $key) use ($id, $uuid) { + $labels = data_get($value, 'Config.Labels'); + $labels = Arr::undot(format_docker_labels_to_json($labels)); + $labelId = data_get($labels, 'coolify.applicationId'); + if ($labelId == $id) { + return $value; + } + $isPR = Str::startsWith(data_get($value, 'Name'), "/$uuid"); + $isPR = Str::contains(data_get($value, 'Name'), "-pr-"); + if ($isPR) { + ray('is pr'); + return false; + } + return $value; })->first(); - + ray($foundContainer); if ($foundContainer) { $containerStatus = data_get($foundContainer, 'State.Status'); $databaseStatus = data_get($application, 'status'); @@ -103,6 +242,19 @@ public function handle(): void $this->server->team->notify(new ContainerStopped($containerName, $this->server, $url)); } } + $previews = $application->previews; + foreach ($previews as $preview) { + $foundContainer = $containers->filter(function ($value, $key) use ($id, $uuid, $preview) { + $labels = data_get($value, 'Config.Labels'); + $labels = Arr::undot(format_docker_labels_to_json($labels)); + $labelId = data_get($labels, 'coolify.applicationId'); + if ($labelId == "$id-pr-{$preview->id}") { + return $value; + } + return Str::startsWith(data_get($value, 'Name'), "/$uuid-pr-{$preview->id}"); + })->first(); + } + } foreach ($databases as $database) { $uuid = data_get($database, 'uuid'); diff --git a/app/Models/Server.php b/app/Models/Server.php index f45cf3b2d..1fd19e119 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -105,6 +105,14 @@ public function applications() })->flatten(); } + public function previews() { + return $this->destinations()->map(function ($standaloneDocker) { + return $standaloneDocker->applications->map(function ($application) { + return $application->previews; + })->flatten(); + })->flatten(); + } + public function destinations() { $standalone_docker = $this->hasMany(StandaloneDocker::class)->get(); diff --git a/app/Notifications/Container/ContainerStopped.php b/app/Notifications/Container/ContainerStopped.php index eec287751..390c9552b 100644 --- a/app/Notifications/Container/ContainerStopped.php +++ b/app/Notifications/Container/ContainerStopped.php @@ -26,7 +26,7 @@ public function via(object $notifiable): array public function toMail(): MailMessage { $mail = new MailMessage(); - $mail->subject("⛔ Container ({$this->name}) has been stopped on {$this->server->name}"); + $mail->subject("⛔ Container {$this->name} has been stopped on {$this->server->name}"); $mail->view('emails.container-stopped', [ 'containerName' => $this->name, 'serverName' => $this->server->name, @@ -37,12 +37,12 @@ public function toMail(): MailMessage public function toDiscord(): string { - $message = "⛔ Container ({$this->name}) has been stopped on {$this->server->name}"; + $message = "⛔ Container {$this->name} has been stopped on {$this->server->name}"; return $message; } public function toTelegram(): array { - $message = "⛔ Container ({$this->name}) has been stopped on {$this->server->name}"; + $message = "⛔ Container ($this->name} has been stopped on {$this->server->name}"; $payload = [ "message" => $message, ]; diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index e3629e71c..03483afdc 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -91,7 +91,7 @@ function generateApplicationContainerName(string $uuid, int $pull_request_id = 0 { $now = now()->format('Hisu'); if ($pull_request_id !== 0 && $pull_request_id !== null) { - return $uuid . '-pr-' . $pull_request_id . '-' . $now; + return $uuid . '-pr-' . $pull_request_id; } else { return $uuid . '-' . $now; } diff --git a/docker/dev-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler-worker b/docker/dev-ssu/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler-worker deleted file mode 100644 index e69de29bb..000000000 diff --git a/resources/views/emails/container-restarted.blade.php b/resources/views/emails/container-restarted.blade.php index e3ec3371c..fef7b02cb 100644 --- a/resources/views/emails/container-restarted.blade.php +++ b/resources/views/emails/container-restarted.blade.php @@ -3,9 +3,13 @@ Container ({{ $containerName }}) has been restarted automatically on {{$serverName}}, because it was stopped unexpected. @if ($containerName === 'coolify-proxy') -Coolify Proxy should run on your server as you have FQDN set up in one of your resources. If you don't want to use Coolify Proxy, please remove FQDN from your resources. +Coolify Proxy should run on your server as you have FQDNs set up in one of your resources. Note: The proxy should not stop unexpectedly, so please check what is going on your server. + + + +If you don't want to use Coolify Proxy, please remove FQDN from your resources or set Proxy type to Custom(None). @endif diff --git a/resources/views/livewire/project/application/previews.blade.php b/resources/views/livewire/project/application/previews.blade.php index 89e3e8a23..554e6606e 100644 --- a/resources/views/livewire/project/application/previews.blade.php +++ b/resources/views/livewire/project/application/previews.blade.php @@ -48,10 +48,10 @@ @endif @if ($application->previews->count() > 0) -

Deployed Previews

+

Deployed Previews

@foreach ($application->previews as $preview) -
+
PR #{{ data_get($preview, 'pull_request_id') }} | @if (data_get($preview, 'status') === 'running') diff --git a/routes/webhooks.php b/routes/webhooks.php index c3c80dba3..aebcfaf14 100644 --- a/routes/webhooks.php +++ b/routes/webhooks.php @@ -116,7 +116,7 @@ $applications = $applications->where('git_branch', $base_branch)->get(); } if ($applications->isEmpty()) { - return response('Nothing to do. No applications found.'); + return response("Nothing to do. No applications found with branch '$base_branch'."); } foreach ($applications as $application) { $isFunctional = $application->destination->server->isFunctional(); @@ -178,6 +178,7 @@ } } } catch (Exception $e) { + ray($e->getMessage()); return general_error_handler(err: $e); } });