fix: lots of regarding git + docker compose deployments

This commit is contained in:
Andras Bacsai 2023-11-27 14:28:21 +01:00
parent 8d86d53292
commit d4d2cc71a0
8 changed files with 135 additions and 63 deletions

View File

@ -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) {

View File

@ -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;
});

View File

@ -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([]);
}

View File

@ -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');

View File

@ -1,6 +1,7 @@
<?php
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\EnvironmentVariable;
use App\Models\InstanceSettings;
use App\Models\LocalFileVolume;
@ -578,7 +579,7 @@ function getTopLevelNetworks(Service|Application $resource)
return $topLevelNetworks->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 = [

View File

@ -24,13 +24,16 @@
helper="Allow Git LFS during build process." />
@endif
<form wire:submit.prevent="submit">
<div class="flex gap-2">
<x-forms.checkbox helper="Enable GPU usage for this application. More info <a href='https://docs.docker.com/compose/gpu-support/' class='text-white underline' target='_blank'>here</a>." instantSave
id="application.settings.is_gpu_enabled" label="GPU Enabled Application" />
@if ($application->settings->is_gpu_enabled)
<x-forms.button type="submiot">Save</x-forms.button>
@endif
</div>
@if ($application->build_pack !== 'dockercompose')
<div class="flex gap-2">
<x-forms.checkbox
helper="Enable GPU usage for this application. More info <a href='https://docs.docker.com/compose/gpu-support/' class='text-white underline' target='_blank'>here</a>."
instantSave id="application.settings.is_gpu_enabled" label="GPU Enabled Application" />
@if ($application->settings->is_gpu_enabled)
<x-forms.button type="submiot">Save</x-forms.button>
@endif
</div>
@endif
@if ($application->settings->is_gpu_enabled)
<div class="flex flex-col w-full gap-2 p-2 xl:flex-row">
<x-forms.input label="GPU Driver" id="application.settings.gpu_driver"> </x-forms.input>

View File

@ -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." />
</div>
@endif
@if ($application->build_pack === 'dockercompose')
@if (count($parsedServices) > 0)
@foreach (data_get($parsedServices, 'services') as $serviceName => $service)
@if (!isDatabaseImage(data_get($service, 'image')))
<div class="flex items-end gap-2">
<x-forms.input
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io, https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "
label="Domains for {{ str($serviceName)->headline() }}"
id="parsedServiceDomains.{{ $serviceName }}.domain"></x-forms.input>
@if (!data_get($parsedServiceDomains, "$serviceName.domain"))
<x-forms.button wire:click="generateDomain('{{ $serviceName }}')">Generate
Domain</x-forms.button>
@endif
</div>
@endif
@endforeach
@endif
@endif
</div>
@endif
@if ($application->build_pack !== 'dockerimage' && $application->build_pack !== 'dockercompose')
@ -119,24 +137,9 @@
@endif
@if ($application->build_pack === 'dockercompose')
<x-forms.button wire:click="loadComposeFile">Reload Compose File</x-forms.button>
@if (count($parsedServices) > 0)
@foreach (data_get($parsedServices, 'services') as $serviceName => $service)
@if (!isDatabaseImage(data_get($service, 'image')))
<div class="flex items-end gap-2">
<x-forms.input
helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io, https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "
label="Domains for {{ str($serviceName)->headline() }}"
id="parsedServiceDomains.{{ $serviceName }}.domain"></x-forms.input>
@if (!data_get($parsedServiceDomains, "$serviceName.domain"))
<x-forms.button wire:click="generateDomain('{{ $serviceName }}')">Generate
Domain</x-forms.button>
@endif
</div>
@endif
@endforeach
@endif
<x-forms.textarea rows="20" readonly id="application.docker_compose"
label="Docker Compose Content" />
<x-forms.textarea rows="10" readonly id="application.docker_compose" label="Docker Compose Content"
helper="You need to modify the docker compose file." />
@endif
@if ($application->dockerfile)

View File

@ -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,