mirror of
https://github.com/tiennm99/coolify.git
synced 2026-04-20 11:21:09 +00:00
fix: properly handle SERVICE_URL and SERVICE_FQDN for abbreviated service names (#7243)
Parse template variables directly instead of generating from container names. Always create both SERVICE_URL and SERVICE_FQDN pairs together. Properly separate scheme handling (URL has scheme, FQDN doesn't). Add comprehensive test coverage. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -115,65 +115,163 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
|
||||
$resource->save();
|
||||
}
|
||||
|
||||
$serviceName = str($resource->name)->upper()->replace('-', '_')->replace('.', '_');
|
||||
$resource->service->environment_variables()->where('key', 'LIKE', "SERVICE_FQDN_{$serviceName}%")->delete();
|
||||
$resource->service->environment_variables()->where('key', 'LIKE', "SERVICE_URL_{$serviceName}%")->delete();
|
||||
// Extract SERVICE_URL and SERVICE_FQDN variable names from the compose template
|
||||
// to ensure we use the exact names defined in the template (which may be abbreviated)
|
||||
// IMPORTANT: Only extract variables that are DIRECTLY DECLARED for this service,
|
||||
// not variables that are merely referenced from other services
|
||||
$serviceConfig = data_get($dockerCompose, "services.{$name}");
|
||||
$environment = data_get($serviceConfig, 'environment', []);
|
||||
$templateVariableNames = [];
|
||||
|
||||
foreach ($environment as $envVar) {
|
||||
if (is_string($envVar)) {
|
||||
// Extract variable name (before '=' if present)
|
||||
$envVarName = str($envVar)->before('=')->trim();
|
||||
// Only include if it's a direct declaration (not a reference like ${VAR})
|
||||
// Direct declarations look like: SERVICE_URL_APP or SERVICE_URL_APP_3000
|
||||
// References look like: NEXT_PUBLIC_URL=${SERVICE_URL_APP}
|
||||
if ($envVarName->startsWith('SERVICE_FQDN_') || $envVarName->startsWith('SERVICE_URL_')) {
|
||||
$templateVariableNames[] = $envVarName->value();
|
||||
}
|
||||
}
|
||||
// DO NOT extract variables that are only referenced with ${VAR_NAME} syntax
|
||||
// Those belong to other services and will be updated when THOSE services are updated
|
||||
}
|
||||
|
||||
// Remove duplicates
|
||||
$templateVariableNames = array_unique($templateVariableNames);
|
||||
|
||||
// Extract unique service names to process (preserving the original case from template)
|
||||
// This allows us to create both URL and FQDN pairs regardless of which one is in the template
|
||||
$serviceNamesToProcess = [];
|
||||
foreach ($templateVariableNames as $templateVarName) {
|
||||
$parsed = parseServiceEnvironmentVariable($templateVarName);
|
||||
|
||||
// Extract the original service name with case preserved from the template
|
||||
$strKey = str($templateVarName);
|
||||
if ($parsed['has_port']) {
|
||||
// For port-specific variables, get the name between SERVICE_URL_/SERVICE_FQDN_ and the last underscore
|
||||
if ($strKey->startsWith('SERVICE_URL_')) {
|
||||
$serviceName = $strKey->after('SERVICE_URL_')->beforeLast('_')->value();
|
||||
} elseif ($strKey->startsWith('SERVICE_FQDN_')) {
|
||||
$serviceName = $strKey->after('SERVICE_FQDN_')->beforeLast('_')->value();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// For base variables, get everything after SERVICE_URL_/SERVICE_FQDN_
|
||||
if ($strKey->startsWith('SERVICE_URL_')) {
|
||||
$serviceName = $strKey->after('SERVICE_URL_')->value();
|
||||
} elseif ($strKey->startsWith('SERVICE_FQDN_')) {
|
||||
$serviceName = $strKey->after('SERVICE_FQDN_')->value();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Use lowercase key for array indexing (to group case variations together)
|
||||
$serviceKey = str($serviceName)->lower()->value();
|
||||
|
||||
// Track both base service name and port-specific variant
|
||||
if (! isset($serviceNamesToProcess[$serviceKey])) {
|
||||
$serviceNamesToProcess[$serviceKey] = [
|
||||
'base' => $serviceName, // Preserve original case
|
||||
'ports' => [],
|
||||
];
|
||||
}
|
||||
|
||||
// If this variable has a port, track it
|
||||
if ($parsed['has_port'] && $parsed['port']) {
|
||||
$serviceNamesToProcess[$serviceKey]['ports'][] = $parsed['port'];
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all existing SERVICE_URL and SERVICE_FQDN variables for these service names
|
||||
// We need to delete both URL and FQDN variants, with and without ports
|
||||
foreach ($serviceNamesToProcess as $serviceInfo) {
|
||||
$serviceName = $serviceInfo['base'];
|
||||
|
||||
// Delete base variables
|
||||
$resource->service->environment_variables()->where('key', "SERVICE_URL_{$serviceName}")->delete();
|
||||
$resource->service->environment_variables()->where('key', "SERVICE_FQDN_{$serviceName}")->delete();
|
||||
|
||||
// Delete port-specific variables
|
||||
foreach ($serviceInfo['ports'] as $port) {
|
||||
$resource->service->environment_variables()->where('key', "SERVICE_URL_{$serviceName}_{$port}")->delete();
|
||||
$resource->service->environment_variables()->where('key', "SERVICE_FQDN_{$serviceName}_{$port}")->delete();
|
||||
}
|
||||
}
|
||||
|
||||
if ($resource->fqdn) {
|
||||
$resourceFqdns = str($resource->fqdn)->explode(',');
|
||||
$resourceFqdns = $resourceFqdns->first();
|
||||
$variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', '_')->replace('.', '_');
|
||||
$url = Url::fromString($resourceFqdns);
|
||||
$port = $url->getPort();
|
||||
$path = $url->getPath();
|
||||
|
||||
// Prepare URL value (with scheme and host)
|
||||
$urlValue = $url->getScheme().'://'.$url->getHost();
|
||||
$urlValue = ($path === '/') ? $urlValue : $urlValue.$path;
|
||||
$resource->service->environment_variables()->updateOrCreate([
|
||||
'resourceable_type' => Service::class,
|
||||
'resourceable_id' => $resource->service_id,
|
||||
'key' => $variableName,
|
||||
], [
|
||||
'value' => $urlValue,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
if ($port) {
|
||||
$variableName = $variableName."_$port";
|
||||
|
||||
// Prepare FQDN value (host only, no scheme)
|
||||
$fqdnHost = $url->getHost();
|
||||
$fqdnValue = str($fqdnHost)->after('://');
|
||||
if ($path !== '/') {
|
||||
$fqdnValue = $fqdnValue.$path;
|
||||
}
|
||||
|
||||
// For each service name found in template, create BOTH SERVICE_URL and SERVICE_FQDN pairs
|
||||
foreach ($serviceNamesToProcess as $serviceInfo) {
|
||||
$serviceName = $serviceInfo['base'];
|
||||
$ports = array_unique($serviceInfo['ports']);
|
||||
|
||||
// ALWAYS create base pair (without port)
|
||||
$resource->service->environment_variables()->updateOrCreate([
|
||||
'resourceable_type' => Service::class,
|
||||
'resourceable_id' => $resource->service_id,
|
||||
'key' => $variableName,
|
||||
'key' => "SERVICE_URL_{$serviceName}",
|
||||
], [
|
||||
'value' => $urlValue,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
}
|
||||
$variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', '_')->replace('.', '_');
|
||||
$fqdn = Url::fromString($resourceFqdns);
|
||||
$port = $fqdn->getPort();
|
||||
$path = $fqdn->getPath();
|
||||
$fqdn = $fqdn->getHost();
|
||||
$fqdnValue = str($fqdn)->after('://');
|
||||
if ($path !== '/') {
|
||||
$fqdnValue = $fqdnValue.$path;
|
||||
}
|
||||
$resource->service->environment_variables()->updateOrCreate([
|
||||
'resourceable_type' => Service::class,
|
||||
'resourceable_id' => $resource->service_id,
|
||||
'key' => $variableName,
|
||||
], [
|
||||
'value' => $fqdnValue,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
if ($port) {
|
||||
$variableName = $variableName."_$port";
|
||||
|
||||
$resource->service->environment_variables()->updateOrCreate([
|
||||
'resourceable_type' => Service::class,
|
||||
'resourceable_id' => $resource->service_id,
|
||||
'key' => $variableName,
|
||||
'key' => "SERVICE_FQDN_{$serviceName}",
|
||||
], [
|
||||
'value' => $fqdnValue,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
|
||||
// Create port-specific pairs for each port found in template or FQDN
|
||||
$allPorts = $ports;
|
||||
if ($port && ! in_array($port, $allPorts)) {
|
||||
$allPorts[] = $port;
|
||||
}
|
||||
|
||||
foreach ($allPorts as $portNum) {
|
||||
$urlWithPort = $urlValue.':'.$portNum;
|
||||
$fqdnWithPort = $fqdnValue.':'.$portNum;
|
||||
|
||||
$resource->service->environment_variables()->updateOrCreate([
|
||||
'resourceable_type' => Service::class,
|
||||
'resourceable_id' => $resource->service_id,
|
||||
'key' => "SERVICE_URL_{$serviceName}_{$portNum}",
|
||||
], [
|
||||
'value' => $urlWithPort,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
|
||||
$resource->service->environment_variables()->updateOrCreate([
|
||||
'resourceable_type' => Service::class,
|
||||
'resourceable_id' => $resource->service_id,
|
||||
'key' => "SERVICE_FQDN_{$serviceName}_{$portNum}",
|
||||
], [
|
||||
'value' => $fqdnWithPort,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
Reference in New Issue
Block a user