From f4803ad58bfb8f7cb12a7c296338767b06f3c66a Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Wed, 29 Nov 2023 14:59:06 +0100 Subject: [PATCH] wip: swarm fix: gitcompose deployments --- app/Actions/Proxy/CheckProxy.php | 55 +++++++++++-------- app/Http/Livewire/Server/Proxy/Deploy.php | 26 +++++++-- app/Http/Livewire/Server/Proxy/Status.php | 2 +- app/Jobs/ApplicationDeploymentJob.php | 34 +++--------- app/Jobs/ContainerStatusJob.php | 43 +++++++++++++-- app/Models/Application.php | 49 +++++++++-------- bootstrap/helpers/docker.php | 20 ++++++- bootstrap/helpers/shared.php | 2 +- .../views/livewire/boarding/index.blade.php | 4 +- .../views/livewire/server/form.blade.php | 4 +- .../views/livewire/server/new/by-ip.blade.php | 4 +- .../livewire/server/proxy/status.blade.php | 2 +- 12 files changed, 149 insertions(+), 96 deletions(-) diff --git a/app/Actions/Proxy/CheckProxy.php b/app/Actions/Proxy/CheckProxy.php index 279fac20e..32673abc9 100644 --- a/app/Actions/Proxy/CheckProxy.php +++ b/app/Actions/Proxy/CheckProxy.php @@ -17,35 +17,42 @@ class CheckProxy return false; } } - $status = getContainerStatus($server, 'coolify-proxy'); - if ($status === 'running') { - $server->proxy->set('status', 'running'); + if ($server->isSwarm()) { + $status = getContainerStatus($server, 'coolify-proxy_traefik'); + $server->proxy->set('status', $status); $server->save(); return false; - } - $ip = $server->ip; - if ($server->id === 0) { - $ip = 'host.docker.internal'; - } + } else { + $status = getContainerStatus($server, 'coolify-proxy'); + if ($status === 'running') { + $server->proxy->set('status', 'running'); + $server->save(); + return false; + } + $ip = $server->ip; + if ($server->id === 0) { + $ip = 'host.docker.internal'; + } - $connection80 = @fsockopen($ip, '80'); - $connection443 = @fsockopen($ip, '443'); - $port80 = is_resource($connection80) && fclose($connection80); - $port443 = is_resource($connection443) && fclose($connection443); - if ($port80) { - if ($fromUI) { - throw new \Exception("Port 80 is in use.
You must stop the process using this port.
Docs: https://coolify.io/docs
Discord: https://coollabs.io/discord"); - } else { - return false; + $connection80 = @fsockopen($ip, '80'); + $connection443 = @fsockopen($ip, '443'); + $port80 = is_resource($connection80) && fclose($connection80); + $port443 = is_resource($connection443) && fclose($connection443); + if ($port80) { + if ($fromUI) { + throw new \Exception("Port 80 is in use.
You must stop the process using this port.
Docs: https://coolify.io/docs
Discord: https://coollabs.io/discord"); + } else { + return false; + } } - } - if ($port443) { - if ($fromUI) { - throw new \Exception("Port 443 is in use.
You must stop the process using this port.
Docs: https://coolify.io/docs
Discord: https://coollabs.io/discord"); - } else { - return false; + if ($port443) { + if ($fromUI) { + throw new \Exception("Port 443 is in use.
You must stop the process using this port.
Docs: https://coolify.io/docs
Discord: https://coollabs.io/discord"); + } else { + return false; + } } + return true; } - return true; } } diff --git a/app/Http/Livewire/Server/Proxy/Deploy.php b/app/Http/Livewire/Server/Proxy/Deploy.php index 7e828b092..9612eccc7 100644 --- a/app/Http/Livewire/Server/Proxy/Deploy.php +++ b/app/Http/Livewire/Server/Proxy/Deploy.php @@ -58,11 +58,25 @@ class Deploy extends Component public function stop() { - instant_remote_process([ - "docker rm -f coolify-proxy", - ], $this->server); - $this->server->proxy->status = 'exited'; - $this->server->save(); - $this->emit('proxyStatusUpdated'); + try { + if ($this->server->isSwarm()) { + instant_remote_process([ + "docker service rm coolify-proxy_traefik", + ], $this->server); + $this->server->proxy->status = 'exited'; + $this->server->save(); + $this->emit('proxyStatusUpdated'); + } else { + instant_remote_process([ + "docker rm -f coolify-proxy", + ], $this->server); + $this->server->proxy->status = 'exited'; + $this->server->save(); + $this->emit('proxyStatusUpdated'); + } + } catch (\Throwable $e) { + return handleError($e, $this); + } + } } diff --git a/app/Http/Livewire/Server/Proxy/Status.php b/app/Http/Livewire/Server/Proxy/Status.php index 8df8f10cd..0c5f274b5 100644 --- a/app/Http/Livewire/Server/Proxy/Status.php +++ b/app/Http/Livewire/Server/Proxy/Status.php @@ -16,7 +16,7 @@ class Status extends Component protected $listeners = ['proxyStatusUpdated', 'startProxyPolling']; public function startProxyPolling() { - $this->polling = true; + $this->checkProxy(); } public function proxyStatusUpdated() { diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index bd6d7436a..d0f18d00c 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -220,8 +220,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->application_deployment_queue->addLogEntry("Creating / updating stack."); $this->execute_remote_command( [ - "docker stack deploy --with-registry-auth --prune --compose-file {$this->configuration_dir}/docker-compose.yml {$this->application->uuid}" + executeInDocker($this->deployment_uuid, "cd {$this->workdir} && docker stack deploy --with-registry-auth -c docker-compose.yml {$this->application->uuid}") ], + [ + "echo 'Stack deployed. It may take a few minutes to fully available in your swarm.'" + ] ); } } @@ -376,7 +379,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $envs->push($env->key . '=' . $env->value); } } - ray($envs); $envs_base64 = base64_encode($envs->implode("\n")); $this->execute_remote_command( [ @@ -442,17 +444,21 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->cleanup_git(); $composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id); $yaml = Yaml::dump($composeFile->toArray(), 10); + ray($composeFile); + ray($this->container_name); $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 + executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}{$this->docker_compose_location}"), "hidden" => true ]); $this->save_environment_variables(); $this->stop_running_container(force: true); + ray($this->pull_request_id); $networkId = $this->application->uuid; if ($this->pull_request_id !== 0) { $networkId = "{$this->application->uuid}-{$this->pull_request_id}"; } + ray($networkId); if ($this->server->isSwarm()) { // TODO } else { @@ -832,26 +838,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->env_args = $this->env_args->implode(' '); } - private function modify_compose_file() - { - // ray("{$this->workdir}{$this->docker_compose_location}"); - $this->execute_remote_command([executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->docker_compose_location}"), "hidden" => true, "save" => 'compose_file']); - if ($this->saved_outputs->get('compose_file')) { - $compose = $this->saved_outputs->get('compose_file'); - } - try { - $yaml = Yaml::parse($compose); - } catch (\Exception $e) { - throw new \Exception($e->getMessage()); - } - $services = data_get($yaml, 'services'); - $topLevelNetworks = collect(data_get($yaml, 'networks', [])); - $definedNetwork = collect([$this->application->uuid]); - - $services = collect($services)->map(function ($service, $serviceName) use ($topLevelNetworks, $definedNetwork) { - $serviceNetworks = collect(data_get($service, 'networks', [])); - }); - } private function generate_compose_file() { $ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array; @@ -952,10 +938,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ] ] ]; - } else { $docker_compose['services'][$this->container_name]['labels'] = $labels; - } if ($this->server->isLogDrainEnabled() && $this->application->isLogDrainEnabled()) { $docker_compose['services'][$this->container_name]['logging'] = [ diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index 30324baab..7063e01cd 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -43,15 +43,37 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted return; }; if ($this->server->isSwarm()) { - + $containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false); + $containerReplicase = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false); } else { - + $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false); + $containerReplicase = null; } - $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false); if (is_null($containers)) { return; } $containers = format_docker_command_output_to_json($containers); + if ($containerReplicase) { + $containerReplicase = format_docker_command_output_to_json($containerReplicase); + foreach ($containerReplicase as $containerReplica) { + $name = data_get($containerReplica, 'Name'); + $containers = $containers->map(function ($container) use ($name, $containerReplica) { + if (data_get($container, 'Spec.Name') === $name) { + $replicas = data_get($containerReplica, 'Replicas'); + $running = str($replicas)->explode('/')[0]; + $total = str($replicas)->explode('/')[1]; + if ($running === $total) { + data_set($container, 'State.Status', 'running'); + data_set($container, 'State.Health.Status', 'healthy'); + } else { + data_set($container, 'State.Status', 'starting'); + data_set($container, 'State.Health.Status', 'unhealthy'); + } + } + return $container; + }); + } + } $applications = $this->server->applications(); $databases = $this->server->databases(); $services = $this->server->services()->get(); @@ -63,10 +85,16 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted $foundServices = []; foreach ($containers as $container) { + if ($this->server->isSwarm()) { + $labels = data_get($container, 'Spec.Labels'); + $uuid = data_get($labels, 'coolify.name'); + } else { + $labels = data_get($container, 'Config.Labels'); + $uuid = data_get($labels, 'com.docker.compose.service'); + } $containerStatus = data_get($container, 'State.Status'); $containerHealth = data_get($container, 'State.Health.Status', 'unhealthy'); $containerStatus = "$containerStatus ($containerHealth)"; - $labels = data_get($container, 'Config.Labels'); $labels = Arr::undot(format_docker_labels_to_json($labels)); $applicationId = data_get($labels, 'coolify.applicationId'); if ($applicationId) { @@ -98,7 +126,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted } } } else { - $uuid = data_get($labels, 'com.docker.compose.service'); if ($uuid) { $database = $databases->where('uuid', $uuid)->first(); if ($database) { @@ -253,7 +280,11 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted // Check if proxy is running $this->server->proxyType(); $foundProxyContainer = $containers->filter(function ($value, $key) { - return data_get($value, 'Name') === '/coolify-proxy'; + if ($this->server->isSwarm()) { + return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik'; + } else { + return data_get($value, 'Name') === '/coolify-proxy'; + } })->first(); if (!$foundProxyContainer) { try { diff --git a/app/Models/Application.php b/app/Models/Application.php index ea7e0a930..e2d93c7a1 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -603,6 +603,7 @@ class Application extends BaseModel { if ($this->docker_compose_raw) { $mainCompose = parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id); + ray($this->docker_compose_pr_raw); 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); } @@ -614,7 +615,7 @@ class Application extends BaseModel function loadComposeFile($isInit = false) { $initialDockerComposeLocation = $this->docker_compose_location; - $initialDockerComposePrLocation = $this->docker_compose_pr_location; + // $initialDockerComposePrLocation = $this->docker_compose_pr_location; if ($this->build_pack === 'dockercompose') { if ($isInit && $this->docker_compose_raw) { return; @@ -623,11 +624,11 @@ class Application extends BaseModel ['commands' => $cloneCommand] = $this->generateGitImportCommands(deployment_uuid: $uuid, only_checkout: true, exec_in_docker: false, custom_base_dir: '.'); $workdir = rtrim($this->base_directory, '/'); $composeFile = $this->docker_compose_location; - $prComposeFile = $this->docker_compose_pr_location; - $fileList = collect([".$composeFile"]); - if ($composeFile !== $prComposeFile) { - $fileList->push(".$prComposeFile"); - } + // $prComposeFile = $this->docker_compose_pr_location; + $fileList = collect([".$workdir$composeFile"]); + // if ($composeFile !== $prComposeFile) { + // $fileList->push(".$prComposeFile"); + // } $commands = collect([ "mkdir -p /tmp/{$uuid} && cd /tmp/{$uuid}", $cloneCommand, @@ -645,24 +646,24 @@ class Application extends BaseModel $this->docker_compose_raw = $composeFileContent; $this->save(); } - if ($composeFile === $prComposeFile) { - $this->docker_compose_pr_raw = $composeFileContent; - $this->save(); - } else { - $commands = collect([ - "cd /tmp/{$uuid}", - "cat .$workdir$prComposeFile", - ]); - $composePrFileContent = instant_remote_process($commands, $this->destination->server, false); - if (!$composePrFileContent) { - $this->docker_compose_pr_location = $initialDockerComposePrLocation; - $this->save(); - throw new \Exception("Could not load compose file from $workdir$prComposeFile"); - } else { - $this->docker_compose_pr_raw = $composePrFileContent; - $this->save(); - } - } + // if ($composeFile === $prComposeFile) { + // $this->docker_compose_pr_raw = $composeFileContent; + // $this->save(); + // } else { + // $commands = collect([ + // "cd /tmp/{$uuid}", + // "cat .$workdir$prComposeFile", + // ]); + // $composePrFileContent = instant_remote_process($commands, $this->destination->server, false); + // if (!$composePrFileContent) { + // $this->docker_compose_pr_location = $initialDockerComposePrLocation; + // $this->save(); + // throw new \Exception("Could not load compose file from $workdir$prComposeFile"); + // } else { + // $this->docker_compose_pr_raw = $composePrFileContent; + // $this->save(); + // } + // } $commands = collect([ "rm -rf /tmp/{$uuid}", diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index d643cce6b..c46c4d558 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -93,7 +93,11 @@ function executeInDocker(string $containerId, string $command) function getContainerStatus(Server $server, string $container_id, bool $all_data = false, bool $throwError = false) { - $container = instant_remote_process(["docker inspect --format '{{json .}}' {$container_id}"], $server, $throwError); + if ($server->isSwarm()) { + $container = instant_remote_process(["docker service ls --filter 'name={$container_id}' --format '{{json .}}' "], $server, $throwError); + } else { + $container = instant_remote_process(["docker inspect --format '{{json .}}' {$container_id}"], $server, $throwError); + } if (!$container) { return 'exited'; } @@ -101,7 +105,19 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data if ($all_data) { return $container[0]; } - return data_get($container[0], 'State.Status', 'exited'); + if ($server->isSwarm()) { + $replicas = data_get($container[0], 'Replicas'); + $replicas = explode('/', $replicas); + $active = (int)$replicas[0]; + $total = (int)$replicas[1]; + if ($active === $total) { + return 'running'; + } else { + return 'starting'; + } + } else { + return data_get($container[0], 'State.Status', 'exited'); + } } function generateApplicationContainerName(Application $application, $pull_request_id = 0) diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 8eb1fd443..3681a6c0c 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -863,7 +863,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $key = Str::of($variableName); $value = Str::of($variable); } - // TODO: here is the problem if ($key->startsWith('SERVICE_FQDN')) { if ($isNew || $savedService->fqdn === null) { $name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower(); @@ -1145,6 +1144,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal data_set($service, 'volumes', $serviceVolumes->toArray()); } } else { + // TODO } // Decide if the service is a database $isDatabase = isDatabaseImage(data_get_str($service, 'image')); diff --git a/resources/views/livewire/boarding/index.blade.php b/resources/views/livewire/boarding/index.blade.php index fb102c7c5..98a2c772a 100644 --- a/resources/views/livewire/boarding/index.blade.php +++ b/resources/views/livewire/boarding/index.blade.php @@ -207,10 +207,10 @@ placeholder="Username to connect to your server. Default is root." label="Username" id="remoteServerUser" /> -
+ {{--
-
+
--}} Check Connection diff --git a/resources/views/livewire/server/form.blade.php b/resources/views/livewire/server/form.blade.php index 528fd32c5..b95d0e938 100644 --- a/resources/views/livewire/server/form.blade.php +++ b/resources/views/livewire/server/form.blade.php @@ -54,8 +54,8 @@ helper="If you are using Cloudflare Tunnels, enable this. It will proxy all ssh requests to your server through Cloudflare.Coolify does not install/setup Cloudflare (cloudflared) on your server." id="server.settings.is_cloudflare_tunnel" label="Cloudflare Tunnel" /> @endif - + {{-- --}} {{-- --}} diff --git a/resources/views/livewire/server/new/by-ip.blade.php b/resources/views/livewire/server/new/by-ip.blade.php index 62268f2fe..6ec358fdc 100644 --- a/resources/views/livewire/server/new/by-ip.blade.php +++ b/resources/views/livewire/server/new/by-ip.blade.php @@ -25,10 +25,10 @@ @endif @endforeach -
+ {{--
-
+
--}} Save New Server diff --git a/resources/views/livewire/server/proxy/status.blade.php b/resources/views/livewire/server/proxy/status.blade.php index 65eebe037..e3184d061 100644 --- a/resources/views/livewire/server/proxy/status.blade.php +++ b/resources/views/livewire/server/proxy/status.blade.php @@ -1,6 +1,6 @@
@if ($server->isFunctional()) -
+
@if (data_get($server, 'proxy.status') === 'running') @elseif (data_get($server, 'proxy.status') === 'restarting')