refactor(parsers): streamline domain handling in applicationParser and improve DNS validation logic

This commit is contained in:
Andras Bacsai
2025-08-11 21:22:26 +02:00
parent d3658e114b
commit 970fd3d9e6
2 changed files with 95 additions and 66 deletions

View File

@@ -231,7 +231,6 @@ class General extends Component
// Refresh parsedServiceDomains to reflect any changes in docker_compose_domains // Refresh parsedServiceDomains to reflect any changes in docker_compose_domains
$this->application->refresh(); $this->application->refresh();
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : []; $this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
ray($this->parsedServiceDomains);
// Convert service names with dots to use underscores for HTML form binding // Convert service names with dots to use underscores for HTML form binding
$sanitizedDomains = []; $sanitizedDomains = [];
foreach ($this->parsedServiceDomains as $serviceName => $domain) { foreach ($this->parsedServiceDomains as $serviceName => $domain) {
@@ -463,33 +462,18 @@ class General extends Component
$this->application->publish_directory = rtrim($this->application->publish_directory, '/'); $this->application->publish_directory = rtrim($this->application->publish_directory, '/');
} }
if ($this->application->build_pack === 'dockercompose') { if ($this->application->build_pack === 'dockercompose') {
// Convert sanitized service names back to original names for storage $this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
$originalDomains = []; if ($this->application->isDirty('docker_compose_domains')) {
foreach ($this->parsedServiceDomains as $key => $value) { foreach ($this->parsedServiceDomains as $service) {
// Find the original service name by checking parsed services
$originalServiceName = $key;
if (isset($this->parsedServices['services'])) {
foreach ($this->parsedServices['services'] as $originalName => $service) {
if (str($originalName)->slug('_')->toString() === $key) {
$originalServiceName = $originalName;
break;
}
}
}
$originalDomains[$originalServiceName] = $value;
}
$this->application->docker_compose_domains = json_encode($originalDomains);
foreach ($originalDomains as $serviceName => $service) {
$domain = data_get($service, 'domain'); $domain = data_get($service, 'domain');
if ($domain) { if ($domain) {
if (! validate_dns_entry($domain, $this->application->destination->server)) { if (! validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', 'Validating DNS failed.', "Make sure you have added the DNS records correctly.<br><br>$domain->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help."); $showToaster && $this->dispatch('error', 'Validating DNS failed.', "Make sure you have added the DNS records correctly.<br><br>$domain->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
} }
}
}
check_domain_usage(resource: $this->application); check_domain_usage(resource: $this->application);
} $this->application->save();
}
if ($this->application->isDirty('docker_compose_domains')) {
$this->resetDefaultLabels(); $this->resetDefaultLabels();
} }
} }

View File

@@ -168,11 +168,13 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
$command = parseCommandFromMagicEnvVariable($key); $command = parseCommandFromMagicEnvVariable($key);
if ($command->value() === 'FQDN') { if ($command->value() === 'FQDN') {
$fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
$originalFqdnFor = str($fqdnFor)->replace('_', '-');
if (str($fqdnFor)->contains('-')) { if (str($fqdnFor)->contains('-')) {
$fqdnFor = str($fqdnFor)->replace('-', '_'); $fqdnFor = str($fqdnFor)->replace('-', '_');
} }
$fqdn = generateFqdn(server: $server, random: "$fqdnFor-$uuid", parserVersion: $resource->compose_parsing_version); // Generated FQDN & URL
$url = generateUrl(server: $server, random: "$fqdnFor-$uuid"); $fqdn = generateFqdn(server: $server, random: "$originalFqdnFor-$uuid", parserVersion: $resource->compose_parsing_version);
$url = generateUrl(server: $server, random: "$originalFqdnFor-$uuid");
$resource->environment_variables()->firstOrCreate([ $resource->environment_variables()->firstOrCreate([
'key' => $key->value(), 'key' => $key->value(),
'resourceable_type' => get_class($resource), 'resourceable_type' => get_class($resource),
@@ -184,6 +186,14 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
]); ]);
if ($resource->build_pack === 'dockercompose') { if ($resource->build_pack === 'dockercompose') {
$domains = collect(json_decode(data_get($resource, 'docker_compose_domains'))) ?? collect([]); $domains = collect(json_decode(data_get($resource, 'docker_compose_domains'))) ?? collect([]);
$domainExists = data_get($domains->get($fqdnFor), 'domain');
$envExists = $resource->environment_variables()->where('key', $key->value())->first();
if (str($domainExists)->replace('http://', '')->replace('https://', '')->value() !== $envExists->value) {
$envExists->update([
'value' => $url,
]);
}
if (is_null($domainExists)) {
// Put URL in the domains array instead of FQDN // Put URL in the domains array instead of FQDN
$domains->put((string) $fqdnFor, [ $domains->put((string) $fqdnFor, [
'domain' => $url, 'domain' => $url,
@@ -191,12 +201,14 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
$resource->docker_compose_domains = $domains->toJson(); $resource->docker_compose_domains = $domains->toJson();
$resource->save(); $resource->save();
} }
}
} elseif ($command->value() === 'URL') { } elseif ($command->value() === 'URL') {
$urlFor = $key->after('SERVICE_URL_')->lower()->value(); $urlFor = $key->after('SERVICE_URL_')->lower()->value();
$originalUrlFor = str($urlFor)->replace('_', '-');
if (str($urlFor)->contains('-')) { if (str($urlFor)->contains('-')) {
$urlFor = str($urlFor)->replace('-', '_'); $urlFor = str($urlFor)->replace('-', '_');
} }
$url = generateUrl(server: $server, random: "$urlFor-$uuid"); $url = generateUrl(server: $server, random: "$originalUrlFor-$uuid");
$resource->environment_variables()->firstOrCreate([ $resource->environment_variables()->firstOrCreate([
'key' => $key->value(), 'key' => $key->value(),
'resourceable_type' => get_class($resource), 'resourceable_type' => get_class($resource),
@@ -208,12 +220,21 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
]); ]);
if ($resource->build_pack === 'dockercompose') { if ($resource->build_pack === 'dockercompose') {
$domains = collect(json_decode(data_get($resource, 'docker_compose_domains'))) ?? collect([]); $domains = collect(json_decode(data_get($resource, 'docker_compose_domains'))) ?? collect([]);
$domainExists = data_get($domains->get($urlFor), 'domain');
$envExists = $resource->environment_variables()->where('key', $key->value())->first();
if ($domainExists !== $envExists->value) {
$envExists->update([
'value' => $url,
]);
}
if (is_null($domainExists)) {
$domains->put((string) $urlFor, [ $domains->put((string) $urlFor, [
'domain' => $url, 'domain' => $url,
]); ]);
$resource->docker_compose_domains = $domains->toJson(); $resource->docker_compose_domains = $domains->toJson();
$resource->save(); $resource->save();
} }
}
} else { } else {
$value = generateEnvValue($command, $resource); $value = generateEnvValue($command, $resource);
$resource->environment_variables()->firstOrCreate([ $resource->environment_variables()->firstOrCreate([
@@ -612,7 +633,7 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
$preview = $resource->previews()->find($preview_id); $preview = $resource->previews()->find($preview_id);
$domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))) ?? collect([]); $domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))) ?? collect([]);
} else { } else {
$domains = collect(json_decode($resource->docker_compose_domains)) ?? collect([]); $domains = collect(json_decode(data_get($resource, 'docker_compose_domains'))) ?? collect([]);
} }
$fqdns = data_get($domains, "$serviceName.domain"); $fqdns = data_get($domains, "$serviceName.domain");
// Generate SERVICE_FQDN & SERVICE_URL for dockercompose // Generate SERVICE_FQDN & SERVICE_URL for dockercompose
@@ -651,11 +672,15 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
$resource->environment_variables()->where('resourceable_type', Application::class) $resource->environment_variables()->where('resourceable_type', Application::class)
->where('resourceable_id', $resource->id) ->where('resourceable_id', $resource->id)
->where('key', 'LIKE', "SERVICE_FQDN_{$serviceNameFormatted}%") ->where('key', 'LIKE', "SERVICE_FQDN_{$serviceNameFormatted}%")
->delete(); ->update([
'value' => null,
]);
$resource->environment_variables()->where('resourceable_type', Application::class) $resource->environment_variables()->where('resourceable_type', Application::class)
->where('resourceable_id', $resource->id) ->where('resourceable_id', $resource->id)
->where('key', 'LIKE', "SERVICE_URL_{$serviceNameFormatted}%") ->where('key', 'LIKE', "SERVICE_URL_{$serviceNameFormatted}%")
->delete(); ->update([
'value' => null,
]);
} }
} }
} }
@@ -903,6 +928,29 @@ function serviceParser(Service $resource): Collection
$parsedServices = collect([]); $parsedServices = collect([]);
$allMagicEnvironments = collect([]); $allMagicEnvironments = collect([]);
// Presave services
foreach ($services as $serviceName => $service) {
$image = data_get_str($service, 'image');
$isDatabase = isDatabaseImage($image, $service);
if ($isDatabase) {
$applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first();
if ($applicationFound) {
$savedService = $applicationFound;
} else {
$savedService = ServiceDatabase::firstOrCreate([
'name' => $serviceName,
'image' => $image,
'service_id' => $resource->id,
]);
}
} else {
$savedService = ServiceApplication::firstOrCreate([
'name' => $serviceName,
'image' => $image,
'service_id' => $resource->id,
]);
}
}
foreach ($services as $serviceName => $service) { foreach ($services as $serviceName => $service) {
$predefinedPort = null; $predefinedPort = null;
$magicEnvironments = collect([]); $magicEnvironments = collect([]);
@@ -1014,7 +1062,6 @@ function serviceParser(Service $resource): Collection
} }
$port = null; $port = null;
} }
// if ($isOneClick) {
if (blank($savedService->fqdn)) { if (blank($savedService->fqdn)) {
if ($fqdnFor) { if ($fqdnFor) {
$fqdn = generateFqdn(server: $server, random: "$fqdnFor-$uuid", parserVersion: $resource->compose_parsing_version); $fqdn = generateFqdn(server: $server, random: "$fqdnFor-$uuid", parserVersion: $resource->compose_parsing_version);
@@ -1031,14 +1078,6 @@ function serviceParser(Service $resource): Collection
$url = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value(); $url = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value();
} }
// } else {
// // For services which are not one-click, if no explicit FQDN is set, leave SERVICE_FQDN_ variables empty
// if (blank($savedService->fqdn)) {
// $fqdn = '';
// } else {
// $fqdn = str($savedService->fqdn)->after('://')->before(':')->prepend(str($savedService->fqdn)->before('://')->append('://'))->value();
// }
// }
if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) { if ($value && get_class($value) === \Illuminate\Support\Stringable::class && $value->startsWith('/')) {
$path = $value->value(); $path = $value->value();
if ($path !== '/') { if ($path !== '/') {
@@ -1054,19 +1093,15 @@ function serviceParser(Service $resource): Collection
if ($url && $port) { if ($url && $port) {
$urlWithPort = "$url:$port"; $urlWithPort = "$url:$port";
} }
ray("urlWithPort: $urlWithPort, fqdnWithPort: $fqdnWithPort", "parserVersion: $resource->compose_parsing_version", 'isVersionGreaterThan4207: '.version_compare('4.0.0-beta.420.7', config('constants.coolify.version'), '>='));
if (is_null($savedService->fqdn)) { if (is_null($savedService->fqdn)) {
if ((int) $resource->compose_parsing_version >= 5 && version_compare(config('constants.coolify.version'), '4.0.0-beta.420.7', '>=')) { if ((int) $resource->compose_parsing_version >= 5 && version_compare(config('constants.coolify.version'), '4.0.0-beta.420.7', '>=')) {
if ($fqdnFor) { if ($fqdnFor) {
ray("setting fqdn(fqdnWithPort) to $fqdnWithPort");
$savedService->fqdn = $fqdnWithPort; $savedService->fqdn = $fqdnWithPort;
} }
if ($urlFor) { if ($urlFor) {
ray("setting fqdn(urlWithPort) to $urlWithPort");
$savedService->fqdn = $urlWithPort; $savedService->fqdn = $urlWithPort;
} }
} else { } else {
ray("setting fqdn(fqdnWithPort) old parser version to $fqdnWithPort");
$savedService->fqdn = $fqdnWithPort; $savedService->fqdn = $fqdnWithPort;
} }
$savedService->save(); $savedService->save();
@@ -1114,7 +1149,6 @@ function serviceParser(Service $resource): Collection
} }
} }
} }
$allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments); $allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments);
if ($magicEnvironments->count() > 0) { if ($magicEnvironments->count() > 0) {
foreach ($magicEnvironments as $key => $value) { foreach ($magicEnvironments as $key => $value) {
@@ -1123,10 +1157,16 @@ function serviceParser(Service $resource): Collection
$command = parseCommandFromMagicEnvVariable($key); $command = parseCommandFromMagicEnvVariable($key);
if ($command->value() === 'FQDN') { if ($command->value() === 'FQDN') {
$fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value();
if (str($fqdnFor)->contains('_')) { $fqdn = generateFqdn(server: $server, random: str($fqdnFor)->replace('_', '-')->value()."-$uuid", parserVersion: $resource->compose_parsing_version);
$fqdnFor = str($fqdnFor)->before('_'); $url = generateUrl(server: $server, random: str($fqdnFor)->replace('_', '-')->value()."-$uuid");
$envExists = $resource->environment_variables()->where('key', $key->value())->first();
$serviceExists = ServiceApplication::where('name', str($fqdnFor)->replace('_', '-')->value())->where('service_id', $resource->id)->first();
if (! $envExists && (data_get($serviceExists, 'name') === str($fqdnFor)->replace('_', '-')->value())) {
// Save URL otherwise it won't work.
$serviceExists->fqdn = $url;
$serviceExists->save();
} }
$fqdn = generateFqdn(server: $server, random: "$fqdnFor-$uuid", parserVersion: $resource->compose_parsing_version);
$resource->environment_variables()->firstOrCreate([ $resource->environment_variables()->firstOrCreate([
'key' => $key->value(), 'key' => $key->value(),
'resourceable_type' => get_class($resource), 'resourceable_type' => get_class($resource),
@@ -1136,22 +1176,27 @@ function serviceParser(Service $resource): Collection
'is_build_time' => false, 'is_build_time' => false,
'is_preview' => false, 'is_preview' => false,
]); ]);
} elseif ($command->value() === 'URL') { } elseif ($command->value() === 'URL') {
$fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); $urlFor = $key->after('SERVICE_URL_')->lower()->value();
if (str($fqdnFor)->contains('_')) { $url = generateUrl(server: $server, random: str($urlFor)->replace('_', '-')->value()."-$uuid");
$fqdnFor = str($fqdnFor)->before('_');
$envExists = $resource->environment_variables()->where('key', $key->value())->first();
$serviceExists = ServiceApplication::where('name', str($urlFor)->replace('_', '-')->value())->where('service_id', $resource->id)->first();
if (! $envExists && (data_get($serviceExists, 'name') === str($urlFor)->replace('_', '-')->value())) {
$serviceExists->fqdn = $url;
$serviceExists->save();
} }
$fqdn = generateFqdn(server: $server, random: "$fqdnFor-$uuid", parserVersion: $resource->compose_parsing_version);
$fqdn = str($fqdn)->replace('http://', '')->replace('https://', '');
$resource->environment_variables()->firstOrCreate([ $resource->environment_variables()->firstOrCreate([
'key' => $key->value(), 'key' => $key->value(),
'resourceable_type' => get_class($resource), 'resourceable_type' => get_class($resource),
'resourceable_id' => $resource->id, 'resourceable_id' => $resource->id,
], [ ], [
'value' => $fqdn, 'value' => $url,
'is_build_time' => false, 'is_build_time' => false,
'is_preview' => false, 'is_preview' => false,
]); ]);
} else { } else {
$value = generateEnvValue($command, $resource); $value = generateEnvValue($command, $resource);
$resource->environment_variables()->firstOrCreate([ $resource->environment_variables()->firstOrCreate([
@@ -1574,7 +1619,7 @@ function serviceParser(Service $resource): Collection
return str($fqdn)->replace('http://', '')->replace('https://', '')->before(':'); return str($fqdn)->replace('http://', '')->replace('https://', '')->before(':');
}); });
$coolifyEnvironments->put('COOLIFY_FQDN', $fqdnsWithoutPort->implode(',')); $coolifyEnvironments->put('COOLIFY_FQDN', $fqdnsWithoutPort->implode(','));
$urls = $fqdns->map(function ($fqdn) { $urls = $fqdns->map(function ($fqdn): Stringable {
return str($fqdn)->after('://')->before(':')->prepend(str($fqdn)->before('://')->append('://')); return str($fqdn)->after('://')->before(':')->prepend(str($fqdn)->before('://')->append('://'));
}); });
$coolifyEnvironments->put('COOLIFY_URL', $urls->implode(',')); $coolifyEnvironments->put('COOLIFY_URL', $urls->implode(','));