oh wow, it is cool!

This commit is contained in:
Andras Bacsai 2023-09-14 15:52:04 +02:00
parent 53c20e1e99
commit 8412802f4d
11 changed files with 194 additions and 24 deletions

View File

@ -2,10 +2,8 @@
namespace App\Http\Livewire\Project\Application; namespace App\Http\Livewire\Project\Application;
use App\Jobs\ApplicationContainerStatusJob;
use App\Jobs\ContainerStatusJob; use App\Jobs\ContainerStatusJob;
use App\Models\Application; use App\Models\Application;
use App\Notifications\Application\StatusChanged;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
@ -23,9 +21,11 @@ class Heading extends Component
public function check_status() public function check_status()
{ {
ray($this->application->destination->server);
dispatch_sync(new ContainerStatusJob($this->application->destination->server)); dispatch_sync(new ContainerStatusJob($this->application->destination->server));
$this->application->refresh(); $this->application->refresh();
$this->application->previews->each(function ($preview) {
$preview->refresh();
});
} }
public function force_deploy_without_cache() public function force_deploy_without_cache()

View File

@ -3,6 +3,7 @@
namespace App\Http\Livewire\Project\Application; namespace App\Http\Livewire\Project\Application;
use App\Jobs\ApplicationContainerStatusJob; use App\Jobs\ApplicationContainerStatusJob;
use App\Jobs\ContainerStatusJob;
use App\Models\Application; use App\Models\Application;
use App\Models\ApplicationPreview; use App\Models\ApplicationPreview;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@ -23,14 +24,6 @@ class Previews extends Component
$this->parameters = get_route_parameters(); $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() public function load_prs()
{ {
try { try {

View File

@ -119,6 +119,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
if ($containers->count() > 0) { if ($containers->count() > 0) {
$this->currently_running_container_name = data_get($containers[0], 'Names'); $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([ $this->application_deployment_queue->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]); ]);
@ -296,7 +299,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// $this->generate_build_env_variables(); // $this->generate_build_env_variables();
// $this->add_build_env_variables_to_dockerfile(); // $this->add_build_env_variables_to_dockerfile();
$this->build_image(); $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() private function prepare_builder_image()
@ -576,10 +583,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function set_labels_for_applications() 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 = [];
$labels[] = 'coolify.managed=true'; $labels[] = 'coolify.managed=true';
$labels[] = 'coolify.version=' . config('version'); $labels[] = 'coolify.version=' . config('version');
$labels[] = 'coolify.applicationId=' . $this->application->id; $labels[] = 'coolify.applicationId=' . $appId;
$labels[] = 'coolify.type=application'; $labels[] = 'coolify.type=application';
$labels[] = 'coolify.name=' . $this->application->name; $labels[] = 'coolify.name=' . $this->application->name;
if ($this->pull_request_id !== 0) { if ($this->pull_request_id !== 0) {

View File

@ -3,10 +3,12 @@
namespace App\Jobs; namespace App\Jobs;
use App\Actions\Proxy\StartProxy; use App\Actions\Proxy\StartProxy;
use App\Models\ApplicationPreview;
use App\Models\Server; use App\Models\Server;
use App\Notifications\Container\ContainerRestarted; use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped; use App\Notifications\Container\ContainerStopped;
use App\Notifications\Server\Unreachable; use App\Notifications\Server\Unreachable;
use Arr;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldBeUnique;
@ -69,6 +71,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypt
$containers = format_docker_command_output_to_json($containers); $containers = format_docker_command_output_to_json($containers);
$applications = $this->server->applications(); $applications = $this->server->applications();
$databases = $this->server->databases(); $databases = $this->server->databases();
$previews = $this->server->previews();
if ($this->server->isProxyShouldRun()) { if ($this->server->isProxyShouldRun()) {
$foundProxyContainer = $containers->filter(function ($value, $key) { $foundProxyContainer = $containers->filter(function ($value, $key) {
return data_get($value, 'Name') === '/coolify-proxy'; return data_get($value, 'Name') === '/coolify-proxy';
@ -78,12 +81,148 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypt
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server)); $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) { foreach ($applications as $application) {
$uuid = data_get($application, 'uuid'); $uuid = data_get($application, 'uuid');
$foundContainer = $containers->filter(function ($value, $key) use ($uuid) { $id = data_get($application, 'id');
return Str::startsWith(data_get($value, 'Name'), "/$uuid"); $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(); })->first();
ray($foundContainer);
if ($foundContainer) { if ($foundContainer) {
$containerStatus = data_get($foundContainer, 'State.Status'); $containerStatus = data_get($foundContainer, 'State.Status');
$databaseStatus = data_get($application, 'status'); $databaseStatus = data_get($application, 'status');
@ -103,6 +242,19 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypt
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url)); $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) { foreach ($databases as $database) {
$uuid = data_get($database, 'uuid'); $uuid = data_get($database, 'uuid');

View File

@ -105,6 +105,14 @@ class Server extends BaseModel
})->flatten(); })->flatten();
} }
public function previews() {
return $this->destinations()->map(function ($standaloneDocker) {
return $standaloneDocker->applications->map(function ($application) {
return $application->previews;
})->flatten();
})->flatten();
}
public function destinations() public function destinations()
{ {
$standalone_docker = $this->hasMany(StandaloneDocker::class)->get(); $standalone_docker = $this->hasMany(StandaloneDocker::class)->get();

View File

@ -26,7 +26,7 @@ class ContainerStopped extends Notification implements ShouldQueue
public function toMail(): MailMessage public function toMail(): MailMessage
{ {
$mail = new 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', [ $mail->view('emails.container-stopped', [
'containerName' => $this->name, 'containerName' => $this->name,
'serverName' => $this->server->name, 'serverName' => $this->server->name,
@ -37,12 +37,12 @@ class ContainerStopped extends Notification implements ShouldQueue
public function toDiscord(): string 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; return $message;
} }
public function toTelegram(): array 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 = [ $payload = [
"message" => $message, "message" => $message,
]; ];

View File

@ -91,7 +91,7 @@ function generateApplicationContainerName(string $uuid, int $pull_request_id = 0
{ {
$now = now()->format('Hisu'); $now = now()->format('Hisu');
if ($pull_request_id !== 0 && $pull_request_id !== null) { if ($pull_request_id !== 0 && $pull_request_id !== null) {
return $uuid . '-pr-' . $pull_request_id . '-' . $now; return $uuid . '-pr-' . $pull_request_id;
} else { } else {
return $uuid . '-' . $now; return $uuid . '-' . $now;
} }

View File

@ -3,9 +3,13 @@
Container ({{ $containerName }}) has been restarted automatically on {{$serverName}}, because it was stopped unexpected. Container ({{ $containerName }}) has been restarted automatically on {{$serverName}}, because it was stopped unexpected.
@if ($containerName === 'coolify-proxy') @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. 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 @endif
</x-emails.layout> </x-emails.layout>

View File

@ -48,10 +48,10 @@
@endif @endif
</div> </div>
@if ($application->previews->count() > 0) @if ($application->previews->count() > 0)
<h4 class="py-4" wire:poll.10000ms='previewRefresh'>Deployed Previews</h4> <h4 class="py-4">Deployed Previews</h4>
<div class="flex gap-6 "> <div class="flex gap-6 ">
@foreach ($application->previews as $preview) @foreach ($application->previews as $preview)
<div class="flex flex-col p-4 bg-coolgray-200 " x-init="$wire.loadStatus('{{ data_get($preview, 'pull_request_id') }}')"> <div class="flex flex-col p-4 bg-coolgray-200">
<div class="flex gap-2">PR #{{ data_get($preview, 'pull_request_id') }} | <div class="flex gap-2">PR #{{ data_get($preview, 'pull_request_id') }} |
@if (data_get($preview, 'status') === 'running') @if (data_get($preview, 'status') === 'running')
<x-status.running /> <x-status.running />

View File

@ -116,7 +116,7 @@ Route::post('/source/github/events', function () {
$applications = $applications->where('git_branch', $base_branch)->get(); $applications = $applications->where('git_branch', $base_branch)->get();
} }
if ($applications->isEmpty()) { 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) { foreach ($applications as $application) {
$isFunctional = $application->destination->server->isFunctional(); $isFunctional = $application->destination->server->isFunctional();
@ -178,6 +178,7 @@ Route::post('/source/github/events', function () {
} }
} }
} catch (Exception $e) { } catch (Exception $e) {
ray($e->getMessage());
return general_error_handler(err: $e); return general_error_handler(err: $e);
} }
}); });