From 7fd0deedb14ae7f496da8e131970cf6163eb9204 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 5 Jun 2024 15:14:44 +0200 Subject: [PATCH] feat: able to add several domains to compose based previews --- app/Console/Commands/ServicesGenerate.php | 1 - app/Jobs/ApplicationDeploymentJob.php | 3 +- app/Livewire/Project/Application/Heading.php | 1 + app/Livewire/Project/Application/Previews.php | 52 +++++++++------ .../Project/Application/PreviewsCompose.php | 56 ++++++++++++++++ app/Models/Application.php | 11 ++-- app/Models/ApplicationPreview.php | 25 ++++++++ bootstrap/helpers/docker.php | 4 +- bootstrap/helpers/shared.php | 54 +++++----------- ...5_101019_add_docker_compose_pr_domains.php | 28 ++++++++ .../components/applications/links.blade.php | 60 +++++++++++------ .../application/previews-compose.blade.php | 7 ++ .../project/application/previews.blade.php | 64 ++++++++++++++----- 13 files changed, 262 insertions(+), 104 deletions(-) create mode 100644 app/Livewire/Project/Application/PreviewsCompose.php create mode 100644 database/migrations/2024_06_05_101019_add_docker_compose_pr_domains.php create mode 100644 resources/views/livewire/project/application/previews-compose.blade.php diff --git a/app/Console/Commands/ServicesGenerate.php b/app/Console/Commands/ServicesGenerate.php index 8ad4dcde3..0471e2324 100644 --- a/app/Console/Commands/ServicesGenerate.php +++ b/app/Console/Commands/ServicesGenerate.php @@ -26,7 +26,6 @@ class ServicesGenerate extends Command */ public function handle() { - // ray()->clearAll(); $files = array_diff(scandir(base_path('templates/compose')), ['.', '..']); $files = array_filter($files, function ($file) { return strpos($file, '.yaml') !== false; diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index d5e60412a..8a949da5a 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -113,7 +113,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted public $tries = 1; public function __construct(int $application_deployment_queue_id) { - ray()->clearAll(); $this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id); $this->application = Application::find($this->application_deployment_queue->application_id); $this->build_pack = data_get($this->application, 'build_pack'); @@ -373,7 +372,7 @@ private function deploy_docker_compose_buildpack() $yaml = $composeFile = $this->application->docker_compose_raw; $this->save_environment_variables(); } else { - $composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id); + $composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id, preview_id: data_get($this, 'preview.id')); $this->save_environment_variables(); if (!is_null($this->env_filename)) { $services = collect($composeFile['services']); diff --git a/app/Livewire/Project/Application/Heading.php b/app/Livewire/Project/Application/Heading.php index 619be693d..473d00671 100644 --- a/app/Livewire/Project/Application/Heading.php +++ b/app/Livewire/Project/Application/Heading.php @@ -25,6 +25,7 @@ public function getListeners() return [ "echo-private:team.{$teamId},ApplicationStatusChanged" => 'check_status', "compose_loaded" => '$refresh', + "update_links" => '$refresh', ]; } public function mount() diff --git a/app/Livewire/Project/Application/Previews.php b/app/Livewire/Project/Application/Previews.php index 1f4a144a9..90815a755 100644 --- a/app/Livewire/Project/Application/Previews.php +++ b/app/Livewire/Project/Application/Previews.php @@ -43,7 +43,7 @@ public function save_preview($preview_id) try { $success = true; $preview = $this->application->previews->find($preview_id); - if (isset($preview->fqdn)) { + if (data_get_str($preview, 'fqdn')->isNotEmpty()) { $preview->fqdn = str($preview->fqdn)->replaceEnd(',', '')->trim(); $preview->fqdn = str($preview->fqdn)->replaceStart(',', '')->trim(); $preview->fqdn = str($preview->fqdn)->trim()->lower(); @@ -79,7 +79,7 @@ public function generate_preview($preview_id) $random = new Cuid2(7); $preview_fqdn = str_replace('{{random}}', $random, $template); $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); - $preview_fqdn = str_replace('{{pr_id}}', $preview_id, $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $preview->pull_request_id, $preview_fqdn); $preview_fqdn = "$schema://$preview_fqdn"; $preview->fqdn = $preview_fqdn; $preview->save(); @@ -88,17 +88,34 @@ public function generate_preview($preview_id) public function add(int $pull_request_id, string|null $pull_request_html_url = null) { try { - $this->setDeploymentUuid(); - $found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first(); - if (!$found && !is_null($pull_request_html_url)) { - ApplicationPreview::create([ - 'application_id' => $this->application->id, - 'pull_request_id' => $pull_request_id, - 'pull_request_html_url' => $pull_request_html_url - ]); + if ($this->application->build_pack === 'dockercompose') { + $this->setDeploymentUuid(); + $found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first(); + if (!$found && !is_null($pull_request_html_url)) { + $found = ApplicationPreview::create([ + 'application_id' => $this->application->id, + 'pull_request_id' => $pull_request_id, + 'pull_request_html_url' => $pull_request_html_url, + 'docker_compose_domains' => $this->application->docker_compose_domains, + ]); + } + $found->generate_preview_fqdn_compose(); + $this->application->refresh(); + } else { + $this->setDeploymentUuid(); + $found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first(); + if (!$found && !is_null($pull_request_html_url)) { + $found = ApplicationPreview::create([ + 'application_id' => $this->application->id, + 'pull_request_id' => $pull_request_id, + 'pull_request_html_url' => $pull_request_html_url, + ]); + } + $this->application->generate_preview_fqdn($pull_request_id); + $this->application->refresh(); + $this->dispatch('update_links'); + $this->dispatch('success', 'Preview added.'); } - $this->application->generate_preview_fqdn($pull_request_id); - $this->application->refresh(); } catch (\Throwable $e) { return handleError($e, $this); } @@ -152,7 +169,7 @@ public function stop(int $pull_request_id) } } GetContainersStatus::dispatchSync($this->application->destination->server); - $this->application->refresh(); + $this->dispatch('reloadWindow'); } catch (\Throwable $e) { return handleError($e, $this); } @@ -172,15 +189,10 @@ public function delete(int $pull_request_id) } ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first()->delete(); $this->application->refresh(); + $this->dispatch('update_links'); + $this->dispatch('success', 'Preview deleted.'); } catch (\Throwable $e) { return handleError($e, $this); } } - - public function previewRefresh() - { - $this->application->previews->each(function ($preview) { - $preview->refresh(); - }); - } } diff --git a/app/Livewire/Project/Application/PreviewsCompose.php b/app/Livewire/Project/Application/PreviewsCompose.php new file mode 100644 index 000000000..85d8d81c9 --- /dev/null +++ b/app/Livewire/Project/Application/PreviewsCompose.php @@ -0,0 +1,56 @@ +service, 'domain'); + $docker_compose_domains = data_get($this->preview, 'docker_compose_domains'); + $docker_compose_domains = json_decode($docker_compose_domains, true); + $docker_compose_domains[$this->serviceName]['domain'] = $domain; + $this->preview->docker_compose_domains = json_encode($docker_compose_domains); + $this->preview->save(); + $this->dispatch('update_links'); + $this->dispatch('success', 'Domain saved.'); + } + public function generate() + { + $domains = collect(json_decode($this->preview->application->docker_compose_domains)) ?? collect(); + $domain = $domains->first(function ($_, $key) { + return $key === $this->serviceName; + }); + if ($domain) { + $domain = data_get($domain, 'domain'); + $url = Url::fromString($domain); + $template = $this->preview->application->preview_url_template; + $host = $url->getHost(); + $schema = $url->getScheme(); + $random = new Cuid2(7); + $preview_fqdn = str_replace('{{random}}', $random, $template); + $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $this->preview->pull_request_id, $preview_fqdn); + $preview_fqdn = "$schema://$preview_fqdn"; + $docker_compose_domains = data_get($this->preview, 'docker_compose_domains'); + $docker_compose_domains = json_decode($docker_compose_domains, true); + $docker_compose_domains[$this->serviceName]['domain'] = $this->service->domain = $preview_fqdn; + $this->preview->docker_compose_domains = json_encode($docker_compose_domains); + $this->preview->save(); + } + $this->dispatch('update_links'); + $this->dispatch('success', 'Domain generated.'); + } +} diff --git a/app/Models/Application.php b/app/Models/Application.php index e0ed328f9..e606735df 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -861,14 +861,10 @@ function parseRawCompose() instant_remote_process($commands, $this->destination->server, false); } - function parseCompose(int $pull_request_id = 0) + function parseCompose(int $pull_request_id = 0, ?int $preview_id = null) { if ($this->docker_compose_raw) { - $mainCompose = parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id); - if ($this->getMorphClass() === 'App\Models\Application' && $this->docker_compose_pr_raw) { - parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, is_pr: true); - } - return $mainCompose; + return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id); } else { return collect([]); } @@ -1052,7 +1048,8 @@ public function parseHealthcheckFromDockerfile($dockerfile, bool $isInit = false } } } - function generate_preview_fqdn(int $pull_request_id) { + function generate_preview_fqdn(int $pull_request_id) + { $preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->id, $pull_request_id); if (is_null(data_get($preview, 'fqdn')) && $this->fqdn) { if (str($this->fqdn)->contains(',')) { diff --git a/app/Models/ApplicationPreview.php b/app/Models/ApplicationPreview.php index 87dce056e..7c7297af2 100644 --- a/app/Models/ApplicationPreview.php +++ b/app/Models/ApplicationPreview.php @@ -2,6 +2,9 @@ namespace App\Models; +use Spatie\Url\Url; +use Visus\Cuid2\Cuid2; + class ApplicationPreview extends BaseModel { protected $guarded = []; @@ -34,4 +37,26 @@ public function application() { return $this->belongsTo(Application::class); } + function generate_preview_fqdn_compose() + { + $domains = collect(json_decode($this->application->docker_compose_domains)) ?? collect(); + foreach ($domains as $service_name => $domain) { + $domain = data_get($domain, 'domain'); + $url = Url::fromString($domain); + $template = $this->application->preview_url_template; + $host = $url->getHost(); + $schema = $url->getScheme(); + $random = new Cuid2(7); + $preview_fqdn = str_replace('{{random}}', $random, $template); + $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $this->pull_request_id, $preview_fqdn); + $preview_fqdn = "$schema://$preview_fqdn"; + $docker_compose_domains = data_get($this, 'docker_compose_domains'); + $docker_compose_domains = json_decode($docker_compose_domains, true); + $docker_compose_domains[$service_name]['domain'] = $preview_fqdn; + $docker_compose_domains = json_encode($docker_compose_domains); + $this->docker_compose_domains = $docker_compose_domains; + $this->save(); + } + } } diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 0ce578758..fb980c296 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -488,8 +488,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview )); } } else { - if ($preview->fqdn) { + if (data_get($preview,'fqdn')) { $domains = Str::of(data_get($preview, 'fqdn'))->explode(','); + } else { + $domains = collect([]); } $labels = $labels->merge(fqdnLabelsForTraefik( uuid: $appUuid, diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 240d78b33..d6b052395 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -657,9 +657,8 @@ function getTopLevelNetworks(Service|Application $resource) } } -function parseDockerComposeFile(Service|Application $resource, bool $isNew = false, int $pull_request_id = 0, bool $is_pr = false) +function parseDockerComposeFile(Service|Application $resource, bool $isNew = false, int $pull_request_id = 0, ?int $preview_id = null) { - // ray()->clearAll(); if ($resource->getMorphClass() === 'App\Models\Service') { if ($resource->docker_compose_raw) { try { @@ -1283,20 +1282,11 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $isSameDockerComposeFile = false; if ($resource->dockerComposePrLocation() === $resource->dockerComposeLocation()) { $isSameDockerComposeFile = true; - $is_pr = false; } - if ($is_pr) { - try { - $yaml = Yaml::parse($resource->docker_compose_pr_raw); - } catch (\Exception $e) { - return; - } - } else { - try { - $yaml = Yaml::parse($resource->docker_compose_raw); - } catch (\Exception $e) { - return; - } + try { + $yaml = Yaml::parse($resource->docker_compose_raw); + } catch (\Exception $e) { + return; } $server = $resource->destination->server; $topLevelVolumes = collect(data_get($yaml, 'volumes', [])); @@ -1330,7 +1320,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if ($pull_request_id !== 0) { $definedNetwork = collect(["{$resource->uuid}-$pull_request_id"]); } - $services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $server, $pull_request_id) { + $services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $server, $pull_request_id, $preview_id) { $serviceVolumes = collect(data_get($service, 'volumes', [])); $servicePorts = collect(data_get($service, 'ports', [])); $serviceNetworks = collect(data_get($service, 'networks', [])); @@ -1716,21 +1706,14 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if ($fqdns) { $fqdns = str($fqdns)->explode(','); if ($pull_request_id !== 0) { - $fqdns = $fqdns->map(function ($fqdn) use ($pull_request_id, $resource) { - $preview = ApplicationPreview::findPreviewByApplicationAndPullId($resource->id, $pull_request_id); - $url = Url::fromString($fqdn); - $template = $resource->preview_url_template; - $host = $url->getHost(); - $schema = $url->getScheme(); - $random = new Cuid2(7); - $preview_fqdn = str_replace('{{random}}', $random, $template); - $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); - $preview_fqdn = str_replace('{{pr_id}}', $pull_request_id, $preview_fqdn); - $preview_fqdn = "$schema://$preview_fqdn"; - $preview->fqdn = $preview_fqdn; - $preview->save(); - return $preview_fqdn; - }); + $preview = $resource->previews()->find($preview_id); + $docker_compose_domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))); + $found_fqdn = data_get($docker_compose_domains, "$serviceName.domain"); + if ($found_fqdn) { + $fqdns = collect($found_fqdn); + } else { + $fqdns = collect([]); + } } $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( uuid: $resource->uuid, @@ -1797,13 +1780,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $resource->docker_compose_raw = Yaml::dump($yaml, 10, 2); $resource->docker_compose = Yaml::dump($finalServices, 10, 2); } else { - if ($is_pr) { - $resource->docker_compose_pr_raw = Yaml::dump($yaml, 10, 2); - $resource->docker_compose_pr = Yaml::dump($finalServices, 10, 2); - } else { - $resource->docker_compose_raw = Yaml::dump($yaml, 10, 2); - $resource->docker_compose = Yaml::dump($finalServices, 10, 2); - } + $resource->docker_compose_raw = Yaml::dump($yaml, 10, 2); + $resource->docker_compose = Yaml::dump($finalServices, 10, 2); } $resource->save(); return collect($finalServices); diff --git a/database/migrations/2024_06_05_101019_add_docker_compose_pr_domains.php b/database/migrations/2024_06_05_101019_add_docker_compose_pr_domains.php new file mode 100644 index 000000000..a4b8ea35b --- /dev/null +++ b/database/migrations/2024_06_05_101019_add_docker_compose_pr_domains.php @@ -0,0 +1,28 @@ +text('docker_compose_domains')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('application_previews', function (Blueprint $table) { + $table->dropColumn('docker_compose_domains'); + }); + } +}; diff --git a/resources/views/components/applications/links.blade.php b/resources/views/components/applications/links.blade.php index a14d0e3ae..cf9e9c029 100644 --- a/resources/views/components/applications/links.blade.php +++ b/resources/views/components/applications/links.blade.php @@ -45,24 +45,48 @@ @endforeach @endif - @if (data_get($application, 'previews', collect([]))->count() > 0) - @foreach (data_get($application, 'previews') as $preview) - @if (data_get($preview, 'fqdn')) - - - - - - - - PR{{ data_get($preview, 'pull_request_id') }} | - {{ data_get($preview, 'fqdn') }} - - @endif - @endforeach + @if (data_get($application, 'previews', collect())->count() > 0) + @if (data_get($application, 'build_pack') === 'dockercompose') + @foreach ($application->previews as $preview) + @foreach (collect(json_decode($preview->docker_compose_domains)) as $fqdn) + @if (data_get($fqdn, 'domain')) + @foreach (explode(',', data_get($fqdn, 'domain')) as $domain) + + + + + + + PR{{ data_get($preview, 'pull_request_id') }} | + {{ getFqdnWithoutPort($domain) }} + + @endforeach + @endif + @endforeach + @endforeach + @else + @foreach (data_get($application, 'previews') as $preview) + @if (data_get($preview, 'fqdn')) + + + + + + + + PR{{ data_get($preview, 'pull_request_id') }} | + {{ data_get($preview, 'fqdn') }} + + @endif + @endforeach + @endif @endif @if (data_get($application, 'ports_mappings_array')) @foreach ($application->ports_mappings_array as $port) diff --git a/resources/views/livewire/project/application/previews-compose.blade.php b/resources/views/livewire/project/application/previews-compose.blade.php new file mode 100644 index 000000000..4e50ad18d --- /dev/null +++ b/resources/views/livewire/project/application/previews-compose.blade.php @@ -0,0 +1,7 @@ +
+ + Save + Generate + Domain +
diff --git a/resources/views/livewire/project/application/previews.blade.php b/resources/views/livewire/project/application/previews.blade.php index f069157a3..e98e0fca2 100644 --- a/resources/views/livewire/project/application/previews.blade.php +++ b/resources/views/livewire/project/application/previews.blade.php @@ -42,11 +42,16 @@ class="dark:text-warning">{{ $application->destination->server->name }}.< - Add + Configure - Deploy + + + + Deploy @@ -58,7 +63,7 @@ class="dark:text-warning">{{ $application->destination->server->name }}.< @if ($application->previews->count() > 0) -
Previews
+

Deployed Previews

@foreach (data_get($application, 'previews') as $previewName => $preview)
@@ -81,21 +86,25 @@ class="dark:text-warning">{{ $application->destination->server->name }}.<
-
- - Save - Generate - Domain -
+ + @if ($application->build_pack === 'dockercompose') +
+ @foreach (collect(json_decode($preview->docker_compose_domains)) as $serviceName => $service) + + @endforeach +
+ @else +
+ + Save + Generate + Domain +
+ @endif
- - @if (data_get($preview, 'status') === 'exited') - Deploy - @else - Redeploy - @endif - + @if (count($parameters) > 0) @@ -111,6 +120,27 @@ class="dark:text-warning">{{ $application->destination->server->name }}.< @endif
+ + @if (data_get($preview, 'status') === 'exited') + + + + + Deploy + @else + + + + + + Redeploy + @endif + @if (data_get($preview, 'status') !== 'exited')