diff --git a/app/Http/Livewire/Project/Application/Previews.php b/app/Http/Livewire/Project/Application/Previews.php index 1c9674011..fd471a007 100644 --- a/app/Http/Livewire/Project/Application/Previews.php +++ b/app/Http/Livewire/Project/Application/Previews.php @@ -72,9 +72,11 @@ protected function setDeploymentUuid() public function stop(int $pull_request_id) { try { - $container_name = generateApplicationContainerName($this->application, $pull_request_id); - - instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false); + $containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id); + foreach ($containers as $container) { + $name = str_replace('/', '', $container['Names']); + instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false); + } ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete(); $this->application->refresh(); } catch (\Throwable $e) { diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index f4982ad7d..6dca962bb 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -393,9 +393,16 @@ private function check_image_locally_or_remotely() private function save_environment_variables() { $envs = collect([]); - foreach ($this->application->environment_variables as $env) { - $envs->push($env->key . '=' . $env->value); + if ($this->pull_request_id !== 0) { + foreach ($this->application->environment_variables_preview as $env) { + $envs->push($env->key . '=' . $env->value); + } + } else { + foreach ($this->application->environment_variables as $env) { + $envs->push($env->key . '=' . $env->value); + } } + ray($envs); $envs_base64 = base64_encode($envs->implode("\n")); $this->execute_remote_command( [ @@ -446,7 +453,12 @@ private function deploy_docker_compose_buildpack() if (data_get($this->application, 'docker_compose_location')) { $this->docker_compose_location = $this->application->docker_compose_location; } - $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name}."); + if ($this->pull_request_id === 0) { + $this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name}."); + } else { + ray('asd'); + $this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}."); + } $this->server->executeRemoteCommand( commands: $this->application->prepareHelperImage($this->deployment_uuid), loggingModel: $this->application_deployment_queue @@ -455,19 +467,24 @@ private function deploy_docker_compose_buildpack() $this->clone_repository(); $this->generate_image_names(); $this->cleanup_git(); - $composeFile = $this->application->parseCompose(); + $composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id); $yaml = Yaml::dump($composeFile->toArray(), 10); $this->docker_compose_base64 = base64_encode($yaml); $this->execute_remote_command([ executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yaml"), "hidden" => true ]); - $this->execute_remote_command([ - "docker network create --attachable '{$this->application->uuid}' >/dev/null || true", "hidden" => true, "ignore_errors" => true - ], [ - "docker network connect {$this->application->uuid} coolify-proxy || true", "hidden" => true, "ignore_errors" => true - ]); $this->save_environment_variables(); $this->stop_running_container(force: true); + + $networkId = $this->application->uuid; + if ($this->pull_request_id !== 0) { + $networkId = "{$this->application->uuid}-{$this->pull_request_id}"; + } + $this->execute_remote_command([ + "docker network create --attachable '{$networkId}' >/dev/null || true", "hidden" => true, "ignore_errors" => true + ], [ + "docker network connect {$networkId} coolify-proxy || true", "hidden" => true, "ignore_errors" => true + ]); $this->start_by_compose_file(); $this->application->loadComposeFile(isInit: false); } @@ -750,6 +767,9 @@ private function clone_repository() $importCommands = $this->generate_git_import_commands(); $this->application_deployment_queue->addLogEntry("\n----------------------------------------"); $this->application_deployment_queue->addLogEntry("Importing {$this->customRepository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->basedir}."); + if ($this->pull_request_id !== 0) { + $this->application_deployment_queue->addLogEntry("Checking out tag pull/{$this->pull_request_id}/head."); + } $this->execute_remote_command( [ $importCommands, "hidden" => true @@ -1171,11 +1191,7 @@ private function stop_running_container(bool $force = false) $this->application_deployment_queue->addLogEntry("Removing old containers."); if ($this->newVersionIsHealthy || $force) { $containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id); - if ($this->pull_request_id !== 0) { - $containers = $containers->filter(function ($container) { - return data_get($container, 'Names') === $this->container_name; - }); - } else { + if ($this->pull_request_id === 0) { $containers = $containers->filter(function ($container) { return data_get($container, 'Names') !== $this->container_name; }); diff --git a/app/Models/Application.php b/app/Models/Application.php index 3b0926659..809d5ff6f 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -589,10 +589,10 @@ public function prepareHelperImage(string $deploymentUuid) ]); return $commands; } - function parseCompose() + function parseCompose(int $pull_request_id = 0) { if ($this->docker_compose_raw) { - return parseDockerComposeFile($this); + return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id); } else { return collect([]); } diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 616e0e864..030aa6aa7 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -192,7 +192,7 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource, } return $payload; } -function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled, $onlyPort = null) +function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null) { $labels = collect([]); $labels->push('traefik.enable=true'); diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index a01017c13..df562d5ff 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -1,6 +1,7 @@ keys(); } } -function parseDockerComposeFile(Service|Application $resource, bool $isNew = false) +function parseDockerComposeFile(Service|Application $resource, bool $isNew = false, int $pull_request_id) { // ray()->clearAll(); if ($resource->getMorphClass() === 'App\Models\Service') { @@ -1095,6 +1096,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } $server = $resource->destination->server; $topLevelVolumes = collect(data_get($yaml, 'volumes', [])); + if ($pull_request_id !== 0) { + $topLevelVolumes = collect([]); + } $topLevelNetworks = collect(data_get($yaml, 'networks', [])); $dockerComposeVersion = data_get($yaml, 'version') ?? '3.8'; $services = data_get($yaml, 'services'); @@ -1108,7 +1112,10 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } } $definedNetwork = collect([$resource->uuid]); - $services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $server) { + 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) { $serviceVolumes = collect(data_get($service, 'volumes', [])); $servicePorts = collect(data_get($service, 'ports', [])); $serviceNetworks = collect(data_get($service, 'networks', [])); @@ -1127,17 +1134,40 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $serviceLabels->push("$removedLabelName=$removedLabel"); } } + $baseName = generateApplicationContainerName($resource, $pull_request_id); + $containerName = "$serviceName-$baseName"; + if ($pull_request_id !== 0) { + if (count($serviceVolumes) > 0) { + $serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $pull_request_id, $topLevelVolumes) { + if (is_string($volume)) { + $volume = str($volume); + if ($volume->contains(':')) { + $name = $volume->before(':'); + $mount = $volume->after(':'); + $newName = $name . "-{$resource->uuid}-$pull_request_id"; + $volume = str("$newName:$mount"); + $topLevelVolumes->put($newName, [ + 'name' => $newName, + ]); + } + } else if (is_array($volume)) { + $volume['source'] = str($volume['source'])->append("-{$resource->uuid}-$pull_request_id"); + } - $containerName = "$serviceName-{$resource->uuid}"; + return $volume->value(); + }); + data_set($service, 'volumes', $serviceVolumes->toArray()); + } + } // Decide if the service is a database $isDatabase = isDatabaseImage(data_get_str($service, 'image')); - $image = data_get_str($service, 'image'); data_set($service, 'is_database', $isDatabase); // Collect/create/update networks if ($serviceNetworks->count() > 0) { foreach ($serviceNetworks as $networkName => $networkDetails) { + ray($networkDetails); $networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) { return $value == $networkName || $key == $networkName; }); @@ -1169,10 +1199,17 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal }); if (!$definedNetworkExists) { foreach ($definedNetwork as $network) { - $topLevelNetworks->put($network, [ - 'name' => $network, - 'external' => true - ]); + if ($pull_request_id !== 0) { + $topLevelNetworks->put($network, [ + 'name' => $network, + 'external' => false + ]); + } else { + $topLevelNetworks->put($network, [ + 'name' => $network, + 'external' => true + ]); + } } } $networks = collect(); @@ -1368,12 +1405,29 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $fqdns = data_get($domains, "$serviceName.domain"); if ($fqdns) { $fqdns = str($fqdns)->explode(','); - $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($resource->uuid, $fqdns, true)); + $uuid = new Cuid2(7); + 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; + }); + } + $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($uuid, $fqdns)); } } } - $defaultLabels = defaultLabels($resource->id, $containerName, type: 'application'); - + $defaultLabels = defaultLabels($resource->id, $containerName, $pull_request_id, type: 'application'); $serviceLabels = $serviceLabels->merge($defaultLabels); if ($server->isLogDrainEnabled() && $resource->isLogDrainEnabled()) { @@ -1392,6 +1446,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal data_set($service, 'container_name', $containerName); data_forget($service, 'volumes.*.content'); data_forget($service, 'volumes.*.isDirectory'); + return $service; }); $finalServices = [ diff --git a/resources/views/livewire/project/application/advanced.blade.php b/resources/views/livewire/project/application/advanced.blade.php index 1193456b7..cd666b954 100644 --- a/resources/views/livewire/project/application/advanced.blade.php +++ b/resources/views/livewire/project/application/advanced.blade.php @@ -24,13 +24,16 @@ helper="Allow Git LFS during build process." /> @endif
-
- - @if ($application->settings->is_gpu_enabled) - Save - @endif -
+ @if ($application->build_pack !== 'dockercompose') +
+ + @if ($application->settings->is_gpu_enabled) + Save + @endif +
+ @endif @if ($application->settings->is_gpu_enabled)
diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index 6a1ddb675..0975dbf4d 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -47,6 +47,24 @@ helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
@endif + @if ($application->build_pack === 'dockercompose') + @if (count($parsedServices) > 0) + @foreach (data_get($parsedServices, 'services') as $serviceName => $service) + @if (!isDatabaseImage(data_get($service, 'image'))) +
+ + @if (!data_get($parsedServiceDomains, "$serviceName.domain")) + Generate + Domain + @endif +
+ @endif + @endforeach + @endif + @endif @endif @if ($application->build_pack !== 'dockerimage' && $application->build_pack !== 'dockercompose') @@ -119,24 +137,9 @@ @endif @if ($application->build_pack === 'dockercompose') Reload Compose File - @if (count($parsedServices) > 0) - @foreach (data_get($parsedServices, 'services') as $serviceName => $service) - @if (!isDatabaseImage(data_get($service, 'image'))) -
- - @if (!data_get($parsedServiceDomains, "$serviceName.domain")) - Generate - Domain - @endif -
- @endif - @endforeach - @endif - + + @endif @if ($application->dockerfile) diff --git a/routes/webhooks.php b/routes/webhooks.php index f59f8f681..17efae2d2 100644 --- a/routes/webhooks.php +++ b/routes/webhooks.php @@ -225,13 +225,9 @@ return response("Nothing to do. No applications found with branch '$base_branch'."); } } - ray($applications); foreach ($applications as $application) { - ray($application); $webhook_secret = data_get($application, 'manual_webhook_secret_github'); - ray($webhook_secret); $hmac = hash_hmac('sha256', request()->getContent(), $webhook_secret); - ray($hmac, $x_hub_signature_256); if (!hash_equals($x_hub_signature_256, $hmac)) { ray('Invalid signature'); continue; @@ -324,7 +320,6 @@ $webhook_secret = data_get($github_app, 'webhook_secret'); $hmac = hash_hmac('sha256', request()->getContent(), $webhook_secret); - ray($hmac, $x_hub_signature_256)->blue(); if (config('app.env') !== 'local') { if (!hash_equals($x_hub_signature_256, $hmac)) { return response('not cool'); @@ -661,12 +656,10 @@ $h1 = Str::of($signature)->after('h1='); $signedPayload = $ts->value . ':' . request()->getContent(); $verify = hash_hmac('sha256', $signedPayload, config('subscription.paddle_webhook_secret')); - ray($verify, $h1->value, hash_equals($verify, $h1->value)); if (!hash_equals($verify, $h1->value)) { return response('Invalid signature.', 400); } $eventType = data_get($payload, 'event_type'); - ray($eventType); $webhook = Webhook::create([ 'type' => 'paddle', 'payload' => $payload,