mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(deployments): pass port-suffixed compose service URL variables
Docker Compose applications dropped the stored SERVICE_URL_<NAME>_<PORT> and SERVICE_FQDN_<NAME>_<PORT> variables and only generated the forms without a port, so compose references to them were blank. Generate the port-suffixed forms from the current domain for runtime and build-time, production and preview deployments. Closes #12427 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
dbd211486b
commit
b9ad48e104
@@ -1938,22 +1938,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if ($this->pull_request_id === 0) {
|
||||
// Generate SERVICE_ variables first for dockercompose
|
||||
if ($this->build_pack === 'dockercompose') {
|
||||
$domains = collect(json_decode($this->application->docker_compose_domains ?: '[]', true) ?: []);
|
||||
|
||||
// Generate SERVICE_FQDN & SERVICE_URL for dockercompose
|
||||
// Env keys always use underscore-normalized names so hyphen/dot storage keys stay valid.
|
||||
foreach ($domains as $forServiceName => $domain) {
|
||||
$parsedDomain = composeDomainEntryString($domain);
|
||||
if (filled($parsedDomain)) {
|
||||
$parsedDomain = str($parsedDomain)->explode(',')->first();
|
||||
$coolifyUrl = Url::fromString($parsedDomain);
|
||||
$coolifyScheme = $coolifyUrl->getScheme();
|
||||
$coolifyFqdn = $coolifyUrl->getHost();
|
||||
$coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null);
|
||||
$serviceEnvKey = str(normalizeComposeServiceName((string) $forServiceName))->upper();
|
||||
$envs->push('SERVICE_URL_'.$serviceEnvKey.'='.$coolifyUrl->__toString());
|
||||
$envs->push('SERVICE_FQDN_'.$serviceEnvKey.'='.$coolifyFqdn);
|
||||
}
|
||||
foreach ($this->composeServiceUrlVariables($this->application->docker_compose_domains) as $key => $value) {
|
||||
$envs->push($key.'='.$value);
|
||||
}
|
||||
|
||||
// Generate SERVICE_NAME for dockercompose services from processed compose
|
||||
@@ -2015,21 +2001,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
} else {
|
||||
// Generate SERVICE_ variables first for dockercompose preview
|
||||
if ($this->build_pack === 'dockercompose') {
|
||||
$domains = collect(json_decode(data_get($this->preview, 'docker_compose_domains') ?: '[]', true) ?: []);
|
||||
|
||||
// Generate SERVICE_FQDN & SERVICE_URL for dockercompose
|
||||
foreach ($domains as $forServiceName => $domain) {
|
||||
$parsedDomain = composeDomainEntryString($domain);
|
||||
if (filled($parsedDomain)) {
|
||||
$parsedDomain = str($parsedDomain)->explode(',')->first();
|
||||
$coolifyUrl = Url::fromString($parsedDomain);
|
||||
$coolifyScheme = $coolifyUrl->getScheme();
|
||||
$coolifyFqdn = $coolifyUrl->getHost();
|
||||
$coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null);
|
||||
$serviceEnvKey = str(normalizeComposeServiceName((string) $forServiceName))->upper();
|
||||
$envs->push('SERVICE_URL_'.$serviceEnvKey.'='.$coolifyUrl->__toString());
|
||||
$envs->push('SERVICE_FQDN_'.$serviceEnvKey.'='.$coolifyFqdn);
|
||||
}
|
||||
foreach ($this->composeServiceUrlVariables(data_get($this->preview, 'docker_compose_domains')) as $key => $value) {
|
||||
$envs->push($key.'='.$value);
|
||||
}
|
||||
|
||||
// Generate SERVICE_NAME for dockercompose services
|
||||
@@ -2112,6 +2085,38 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* SERVICE_URL_* and SERVICE_FQDN_* values from the compose domains. Port-suffixed variables
|
||||
* that the parser stored (SERVICE_URL_APP_3000) get the same value with the port appended.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function composeServiceUrlVariables(?string $domainsJson): array
|
||||
{
|
||||
$storedKeys = $this->application->environment_variables()->pluck('key');
|
||||
$variables = [];
|
||||
foreach (json_decode($domainsJson ?: '[]', true) ?: [] as $forServiceName => $domain) {
|
||||
$parsedDomain = composeDomainEntryString($domain);
|
||||
if (blank($parsedDomain)) {
|
||||
continue;
|
||||
}
|
||||
$url = Url::fromString(str($parsedDomain)->explode(',')->first())->withPort(null);
|
||||
$values = ['URL' => $url->__toString(), 'FQDN' => $url->getHost()];
|
||||
$serviceEnvKey = str(normalizeComposeServiceName((string) $forServiceName))->upper()->value();
|
||||
foreach ($values as $type => $value) {
|
||||
$variables["SERVICE_{$type}_{$serviceEnvKey}"] = $value;
|
||||
}
|
||||
$pattern = '/^SERVICE_(URL|FQDN)_'.preg_quote($serviceEnvKey, '/').'_(\d+)$/';
|
||||
foreach ($storedKeys as $key) {
|
||||
if (preg_match($pattern, $key, $matches)) {
|
||||
$variables[$key] = "{$values[$matches[1]]}:{$matches[2]}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $variables;
|
||||
}
|
||||
|
||||
private function isGeneratedDockerComposeEnvironmentVariable(EnvironmentVariable $environmentVariable): bool
|
||||
{
|
||||
$key = str($environmentVariable->key);
|
||||
@@ -2333,19 +2338,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
|
||||
// Generate SERVICE_FQDN & SERVICE_URL for non-PR deployments
|
||||
$domains = collect(json_decode($this->application->docker_compose_domains ?: '[]', true) ?: []);
|
||||
foreach ($domains as $forServiceName => $domain) {
|
||||
$parsedDomain = composeDomainEntryString($domain);
|
||||
if (filled($parsedDomain)) {
|
||||
$parsedDomain = str($parsedDomain)->explode(',')->first();
|
||||
$coolifyUrl = Url::fromString($parsedDomain);
|
||||
$coolifyScheme = $coolifyUrl->getScheme();
|
||||
$coolifyFqdn = $coolifyUrl->getHost();
|
||||
$coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null);
|
||||
$serviceEnvKey = str(normalizeComposeServiceName((string) $forServiceName))->upper();
|
||||
$envs_dict['SERVICE_URL_'.$serviceEnvKey] = escapeBashEnvValue($coolifyUrl->__toString());
|
||||
$envs_dict['SERVICE_FQDN_'.$serviceEnvKey] = escapeBashEnvValue($coolifyFqdn);
|
||||
}
|
||||
foreach ($this->composeServiceUrlVariables($this->application->docker_compose_domains) as $key => $value) {
|
||||
$envs_dict[$key] = escapeBashEnvValue($value);
|
||||
}
|
||||
} else {
|
||||
// Generate SERVICE_NAME for preview deployments
|
||||
@@ -2356,19 +2350,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
|
||||
// Generate SERVICE_FQDN & SERVICE_URL for preview deployments with PR-specific domains
|
||||
$domains = collect(json_decode(data_get($this->preview, 'docker_compose_domains') ?: '[]', true) ?: []);
|
||||
foreach ($domains as $forServiceName => $domain) {
|
||||
$parsedDomain = composeDomainEntryString($domain);
|
||||
if (filled($parsedDomain)) {
|
||||
$parsedDomain = str($parsedDomain)->explode(',')->first();
|
||||
$coolifyUrl = Url::fromString($parsedDomain);
|
||||
$coolifyScheme = $coolifyUrl->getScheme();
|
||||
$coolifyFqdn = $coolifyUrl->getHost();
|
||||
$coolifyUrl = $coolifyUrl->withScheme($coolifyScheme)->withHost($coolifyFqdn)->withPort(null);
|
||||
$serviceEnvKey = str(normalizeComposeServiceName((string) $forServiceName))->upper();
|
||||
$envs_dict['SERVICE_URL_'.$serviceEnvKey] = escapeBashEnvValue($coolifyUrl->__toString());
|
||||
$envs_dict['SERVICE_FQDN_'.$serviceEnvKey] = escapeBashEnvValue($coolifyFqdn);
|
||||
}
|
||||
foreach ($this->composeServiceUrlVariables(data_get($this->preview, 'docker_compose_domains')) as $key => $value) {
|
||||
$envs_dict[$key] = escapeBashEnvValue($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2143,3 +2143,60 @@ YAML;
|
||||
'production' => 0,
|
||||
'preview' => 42,
|
||||
]);
|
||||
|
||||
it('passes port-suffixed compose service URL variables to production and preview deployments', function (int $pullRequestId) {
|
||||
$compose = <<<'YAML'
|
||||
services:
|
||||
docmost:
|
||||
build: .
|
||||
environment:
|
||||
- SERVICE_URL_DOCMOST_3000
|
||||
- APP_URL=$SERVICE_URL_DOCMOST_3000
|
||||
YAML;
|
||||
|
||||
[$application, $server] = makeDeploymentControlVarFixture([
|
||||
'build_pack' => 'dockercompose',
|
||||
'docker_compose_raw' => $compose,
|
||||
'docker_compose' => $compose,
|
||||
'docker_compose_domains' => json_encode(['docmost' => ['domain' => 'https://docs.example.com']]),
|
||||
]);
|
||||
|
||||
foreach ([
|
||||
'SERVICE_URL_DOCMOST' => 'https://docs.example.com',
|
||||
'SERVICE_FQDN_DOCMOST' => 'docs.example.com',
|
||||
'SERVICE_URL_DOCMOST_3000' => 'https://stale.example.com:3000',
|
||||
'SERVICE_FQDN_DOCMOST_3000' => 'stale.example.com:3000',
|
||||
] as $key => $value) {
|
||||
createApplicationEnvironmentVariable($application, ['key' => $key, 'value' => $value, 'is_runtime' => true, 'is_buildtime' => true]);
|
||||
}
|
||||
|
||||
$overrides = ['pull_request_id' => $pullRequestId];
|
||||
$expectedHost = 'docs.example.com';
|
||||
if ($pullRequestId !== 0) {
|
||||
$expectedHost = '7.docs.example.com';
|
||||
$overrides['preview'] = ApplicationPreview::create([
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $pullRequestId,
|
||||
'pull_request_html_url' => 'https://github.com/example/repo/pull/7',
|
||||
'docker_compose_domains' => json_encode(['docmost' => ['domain' => "https://{$expectedHost}"]]),
|
||||
]);
|
||||
}
|
||||
|
||||
[$job, $reflection] = makeControlVarFilteringJob($application, $server, $overrides);
|
||||
|
||||
$runtimeEnvs = invokeDeploymentJobMethod($job, $reflection, 'generate_runtime_environment_variables');
|
||||
expect($runtimeEnvs)
|
||||
->toContain("SERVICE_URL_DOCMOST=https://{$expectedHost}")
|
||||
->toContain("SERVICE_URL_DOCMOST_3000=https://{$expectedHost}:3000")
|
||||
->toContain("SERVICE_FQDN_DOCMOST_3000={$expectedHost}:3000")
|
||||
->and($runtimeEnvs->implode("\n"))->not->toContain('stale.example.com');
|
||||
|
||||
$buildtimeEnvs = invokeDeploymentJobMethod($job, $reflection, 'generate_buildtime_environment_variables')->implode("\n");
|
||||
expect($buildtimeEnvs)
|
||||
->toContain("SERVICE_URL_DOCMOST_3000='https://{$expectedHost}:3000'")
|
||||
->toContain("SERVICE_FQDN_DOCMOST_3000='{$expectedHost}:3000'")
|
||||
->not->toContain('stale.example.com');
|
||||
})->with([
|
||||
'production' => [0],
|
||||
'preview' => [7],
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user