fix: parser parser parser

This commit is contained in:
Andras Bacsai 2024-08-22 15:05:04 +02:00
parent cf505fa500
commit 1e24ab9146
7 changed files with 244 additions and 28 deletions

View File

@ -472,7 +472,7 @@ private function deploy_docker_compose_buildpack()
return;
}
$yaml = Yaml::dump($composeFile->toArray(), 10);
$yaml = Yaml::dump(convertToArray($composeFile), 10);
}
$this->docker_compose_base64 = base64_encode($yaml);
$this->execute_remote_command([
@ -559,6 +559,7 @@ private function deploy_docker_compose_buildpack()
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
);
$this->write_deployment_configurations();
}
}

View File

@ -204,7 +204,7 @@ public function loadComposeFile($isInit = false)
return;
}
$compose = $this->application->parseCompose();
$this->application->parseCompose();
$this->dispatch('success', 'Docker compose file loaded.');
$this->dispatch('compose_loaded');
$this->dispatch('refreshStorages');

View File

@ -19,7 +19,7 @@ public function mount()
$destination_uuid = request()->query('destination');
$server_id = request()->query('server_id');
$database_image = request()->query('database_image');
ray($database_image);
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (! $project) {
return redirect()->route('dashboard');

View File

@ -1106,6 +1106,10 @@ public function parseCompose(int $pull_request_id = 0, ?int $preview_id = null)
if (! $this->docker_compose_raw) {
return collect([]);
}
// $compose = dockerComposeParserForApplications($this);
// return $compose;
$isNew = false;
$isSameDockerComposeFile = false;

View File

@ -5,6 +5,7 @@
use App\Jobs\ServerFilesFromServerJob;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\ApplicationPreview;
use App\Models\EnvironmentVariable;
use App\Models\InstanceSettings;
use App\Models\LocalFileVolume;
@ -780,15 +781,16 @@ function replaceLocalSource(Stringable $source, Stringable $replacedWith)
return $source;
}
function dockerComposeParserForApplications(Application $application, Collection $compose): Collection
function dockerComposeParserForApplications(Application $application): Collection
{
$isPullRequest = data_get($application, 'pull_request_id', 0) === 0 ? false : true;
$pullRequestId = data_get($application, 'pull_request_id', 0);
$isPullRequest = $pullRequestId === 0 ? false : true;
$uuid = data_get($application, 'uuid');
$pullRequestId = data_get($application, 'pull_request_id');
$server = data_get($application, 'destination.server');
$services = data_get($compose, 'services', collect([]));
$compose = data_get($application, 'docker_compose_raw');
$yaml = Yaml::parse($compose);
$services = data_get($yaml, 'services', collect([]));
$topLevel = collect([
'volumes' => collect(data_get($compose, 'volumes', [])),
'networks' => collect(data_get($compose, 'networks', [])),
@ -817,11 +819,26 @@ function dockerComposeParserForApplications(Application $application, Collection
// Let's loop through the services
foreach ($services as $serviceName => $service) {
$isDatabase = isDatabaseImage(data_get_str($service, 'image'));
$image = data_get_str($service, 'image');
$restart = data_get_str($service, 'restart', RESTART_MODE);
$logging = data_get($service, 'logging');
$healthcheck = data_get($service, 'healthcheck');
if ($server->isLogDrainEnabled() && $application->isLogDrainEnabled()) {
$logging = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
}
$volumes = collect(data_get($service, 'volumes', []));
$ports = collect(data_get($service, 'ports', []));
$networks = collect(data_get($service, 'networks', []));
$dependencies = collect(data_get($service, 'depends_on', []));
$depends_on = collect(data_get($service, 'depends_on', []));
$labels = collect(data_get($service, 'labels', []));
$environment = collect(data_get($service, 'environment', []));
$buildArgs = collect(data_get($service, 'build.args', []));
@ -895,7 +912,7 @@ function dockerComposeParserForApplications(Application $application, Collection
$source = $source."-pr-$pullRequestId";
}
if (
! $application->settings->is_preserve_repository_enabled || $foundConfig->is_based_on_git
! $application?->settings?->is_preserve_repository_enabled || $foundConfig?->is_based_on_git
) {
// ray([
// 'fs_path' => $source->value(),
@ -969,12 +986,11 @@ function dockerComposeParserForApplications(Application $application, Collection
$volumesParsed->put($index, $volume);
}
}
if ($topLevel->get('dependencies')?->count() > 0) {
if ($depends_on?->count() > 0) {
if ($isPullRequest) {
$topLevel->get('dependencies')->transform(function ($dependency) use ($pullRequestId) {
$depends_on->transform(function ($dependency) use ($pullRequestId) {
return "$dependency-pr-$pullRequestId";
});
data_set($service, 'depends_on', $topLevel->get('dependencies')->toArray());
}
}
@ -1087,11 +1103,11 @@ function dockerComposeParserForApplications(Application $application, Collection
$fqdn = "$fqdn$path";
}
ray([
'key' => $key,
'value' => $fqdn,
]);
ray($application->environment_variables()->where('key', $key)->where('application_id', $application->id)->first());
// ray([
// 'key' => $key,
// 'value' => $fqdn,
// ]);
// ray($application->environment_variables()->where('key', $key)->where('application_id', $application->id)->first());
$application->environment_variables()->where('key', $key)->where('application_id', $application->id)->firstOrCreate([
'key' => $key,
'application_id' => $application->id,
@ -1154,20 +1170,179 @@ function dockerComposeParserForApplications(Application $application, Collection
$environment = $application->environment_variables()->where('application_id', $application->id)->get()->mapWithKeys(function ($item) {
return [$item['key'] => $item['value']];
});
$parsedServices->put($serviceName, [
// Labels
$fqdns = collect([]);
if ($application?->serviceType()) {
$fqdns = generateServiceSpecificFqdns($application);
} else {
$domains = collect(json_decode($application->docker_compose_domains)) ?? collect([]);
if ($domains->count() !== 0) {
$fqdns = data_get($domains, "$serviceName.domain");
if (! $fqdns) {
$fqdns = collect([]);
} else {
$fqdns = str($fqdns)->explode(',');
if ($isPullRequest) {
$preview = $application->previews()->find($pullRequestId);
$docker_compose_domains = collect(json_decode(data_get($preview, 'docker_compose_domains')));
if ($docker_compose_domains->count() > 0) {
$found_fqdn = data_get($docker_compose_domains, "$serviceName.domain");
if ($found_fqdn) {
$fqdns = collect($found_fqdn);
} else {
$fqdns = collect([]);
}
} else {
$fqdns = $fqdns->map(function ($fqdn) use ($pullRequestId) {
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->id, $pullRequestId);
$url = Url::fromString($fqdn);
$template = $this->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2;
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$preview->fqdn = $preview_fqdn;
$preview->save();
return $preview_fqdn;
});
}
}
$shouldGenerateLabelsExactly = $server->settings->generate_exact_labels;
if ($shouldGenerateLabelsExactly) {
switch ($server->proxyType()) {
case ProxyTypes::TRAEFIK->value:
$labels = $labels->merge(
fqdnLabelsForTraefik(
uuid: $application->uuid,
domains: $fqdns,
serviceLabels: $labels,
generate_unique_uuid: $application->build_pack === 'dockercompose',
image: $image,
is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(),
)
);
break;
case ProxyTypes::CADDY->value:
$labels = $labels->merge(
fqdnLabelsForCaddy(
network: $application->destination->network,
uuid: $application->uuid,
domains: $fqdns,
serviceLabels: $labels,
image: $image,
is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(),
)
);
break;
}
} else {
$labels = $labels->merge(
fqdnLabelsForTraefik(
uuid: $application->uuid,
domains: $fqdns,
serviceLabels: $labels,
generate_unique_uuid: $application->build_pack === 'dockercompose',
image: $image,
is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(),
)
);
$labels = $labels->merge(
fqdnLabelsForCaddy(
network: $application->destination->network,
uuid: $application->uuid,
domains: $fqdns,
serviceLabels: $labels,
image: $image,
is_force_https_enabled: $application->isForceHttpsEnabled(),
is_gzip_enabled: $application->isGzipEnabled(),
is_stripprefix_enabled: $application->isStripprefixEnabled(),
)
);
}
}
}
}
$defaultLabels = defaultLabels(
id: $application->id,
name: $containerName,
pull_request_id: $pullRequestId,
type: 'application');
$labels = $labels->merge($defaultLabels);
if ($labels->count() > 0 && $application->settings->is_container_label_escape_enabled) {
$labels = $labels->map(function ($value, $key) {
return escapeDollarSign($value);
});
}
$payload = [
'image' => $image,
'restart' => $restart,
'container_name' => $containerName,
'volumes' => $volumesParsed,
'ports' => $ports,
'networks' => $networks_temp,
'dependencies' => $dependencies,
'labels' => $labels,
'environment' => $environment,
]);
];
if ($ports->count() > 0) {
$payload['ports'] = $ports;
}
if ($logging) {
$payload['logging'] = $logging;
}
if ($depends_on->count() > 0) {
$payload['depends_on'] = $depends_on;
}
if ($healthcheck) {
$payload['healthcheck'] = $healthcheck;
}
$parsedServices->put($serviceName, $payload);
}
$topLevel->put('services', $parsedServices);
$customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets'];
$topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) {
return array_search($key, $customOrder);
});
$application->docker_compose = Yaml::dump(convertToArray($topLevel), 10, 2);
data_forget($application, 'environment_variables');
data_forget($application, 'environment_variables_preview');
$application->save();
return $topLevel;
}
function convertToArray($collection)
{
if ($collection instanceof Collection) {
return $collection->map(function ($item) {
return convertToArray($item);
})->toArray();
} elseif ($collection instanceof Stringable) {
return (string) $collection;
} elseif (is_array($collection)) {
return array_map(function ($item) {
return convertToArray($item);
}, $collection);
}
return $collection;
}
function parseDockerComposeFile(Service|Application $resource, bool $isNew = false, int $pull_request_id = 0, ?int $preview_id = null)
{
if ($resource->getMorphClass() === 'App\Models\Service') {

View File

@ -254,6 +254,9 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
helper="You need to modify the docker compose file." monacoEditorLanguage="yaml"
useMonacoEditor />
@else
{{-- <x-forms.textarea rows="10" readonly id="application.docker_compose_raw"
label="Docker Compose Content (raw)" helper="You need to modify the docker compose file."
monacoEditorLanguage="yaml" useMonacoEditor /> --}}
<x-forms.textarea rows="10" readonly id="application.docker_compose"
label="Docker Compose Content" helper="You need to modify the docker compose file."
monacoEditorLanguage="yaml" useMonacoEditor />

View File

@ -24,7 +24,30 @@
'./:/var/www/html',
'./nginx:/etc/nginx',
],
'depends_on' => [
'db' => [
'condition' => 'service_healthy',
],
],
],
'db' => [
'image' => 'postgres',
'environment' => [
'POSTGRES_USER' => 'postgres',
'POSTGRES_PASSWORD' => 'postgres',
],
'volumes' => [
'dbdata:/var/lib/postgresql/data',
],
'healthcheck' => [
'test' => ['CMD', 'pg_isready', '-U', 'postgres'],
'interval' => '2s',
'timeout' => '10s',
'retries' => 10,
],
],
],
'networks' => [
'default' => [
@ -32,12 +55,11 @@
],
],
];
$this->composeFileString = Yaml::dump($this->composeFile, 4, 2);
$this->composeFileString = Yaml::dump($this->composeFile, 10, 2);
$this->jsonComposeFile = json_encode($this->composeFile, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);
$this->application = Application::create([
'name' => 'Application for tests',
'fqdn' => 'http://test.com',
'repository_project_id' => 603035348,
'git_repository' => 'coollabsio/coolify-examples',
'git_branch' => 'main',
@ -59,15 +81,26 @@
});
test('ComposeParse', function () {
// expect($this->jsonComposeFile)->toBeJson()->ray();
expect($this->jsonComposeFile)->toBeJson()->ray();
$yaml = Yaml::parse($this->jsonComposeFile);
$output = dockerComposeParserForApplications(
application: $this->application,
compose: collect($yaml),
);
$outputOld = $this->application->parseCompose();
expect($output)->toBeInstanceOf(Collection::class)->ray();
expect($outputOld)->toBeInstanceOf(Collection::class)->ray();
// Test if image is parsed correctly
$image = data_get_str($output, 'services.app.image');
expect($image->value())->toBe('nginx');
$imageOld = data_get_str($outputOld, 'services.app.image');
expect($image->value())->toBe($imageOld->value());
// Test environment variables are parsed correctly
$environment = data_get_str($output, 'services.app.environment');
$service_fqdn_app = data_get_str($environment, 'SERVICE_FQDN_APP');
});
test('DockerBinaryAvailableOnLocalhost', function () {