fix(services): treat SERVICE_FQDN_ twin as a copy of SERVICE_URL_ declaration

A service that declares SERVICE_FQDN_X_PORT while the named service declares
SERVICE_URL_X_PORT (same value) now counts as a copy, like an exact copy, so it
does not take the named service's domain. Also replace the Postgres-only
regex in the magic variable loop with the same check in PHP.
This commit is contained in:
Andras Bacsai
2026-10-18 11:29:39 +02:00
parent 4e7d73e6e2
commit fc4562ede7
3 changed files with 40 additions and 8 deletions
+14 -4
View File
@@ -594,7 +594,7 @@ function findServiceApplicationForEnvName(Service $resource, string $envServiceN
/**
* Check if a service copies a SERVICE_FQDN_ / SERVICE_URL_ declaration of the application named in it.
* The copy (same key, same value) must not generate a domain or overwrite the variable (#11924).
* The copy (same name, port and value; URL or FQDN) must not generate a domain or overwrite the variable (#11924).
* Declarations with another port or path share the host on purpose (for example Appwrite).
*
* @param array<string, mixed>|Collection<string, mixed> $services Parsed compose services
@@ -618,9 +618,19 @@ function isCopiedServiceUrlDeclaration(
$ownerDeclarations = $declarationsOf($owner->name);
$currentDeclarations = $declarationsOf($current->name);
return $ownerDeclarations->has($key)
&& $currentDeclarations->has($key)
&& (string) $ownerDeclarations->get($key) === (string) $currentDeclarations->get($key);
if (! $currentDeclarations->has($key)) {
return false;
}
// SERVICE_URL_X_3000 and SERVICE_FQDN_X_3000 write the same variable pair, so either one counts.
$twinKey = str_starts_with($key, 'SERVICE_URL_')
? 'SERVICE_FQDN_'.substr($key, strlen('SERVICE_URL_'))
: 'SERVICE_URL_'.substr($key, strlen('SERVICE_FQDN_'));
$value = (string) $currentDeclarations->get($key);
return collect([$key, $twinKey])->contains(
fn (string $ownerKey) => $ownerDeclarations->has($ownerKey) && (string) $ownerDeclarations->get($ownerKey) === $value
);
}
/**
+4 -4
View File
@@ -2671,8 +2671,8 @@ function serviceParser(Service $resource): Collection
// Also check if a port-suffixed version exists (e.g., SERVICE_FQDN_UMAMI_3000)
$portSuffixedExists = $resource->environment_variables()
->where('key', 'LIKE', $key->value().'_%')
->whereRaw('key ~ ?', ['^'.$key->value().'_[0-9]+$'])
->exists();
->pluck('key')
->contains(fn ($envKey) => preg_match('/^'.preg_quote($key->value(), '/').'_[0-9]+$/', $envKey) === 1);
$serviceExists = findServiceApplicationForEnvName($resource, (string) $fqdnFor);
// Check if FQDN already has a port set (contains ':' after the domain)
$fqdnHasPort = $serviceExists && str($serviceExists->fqdn)->contains(':') && str($serviceExists->fqdn)->afterLast(':')->isMatch('/^\d+$/');
@@ -2716,8 +2716,8 @@ function serviceParser(Service $resource): Collection
// Also check if a port-suffixed version exists (e.g., SERVICE_URL_DASHBOARD_6791)
$portSuffixedExists = $resource->environment_variables()
->where('key', 'LIKE', $key->value().'_%')
->whereRaw('key ~ ?', ['^'.$key->value().'_[0-9]+$'])
->exists();
->pluck('key')
->contains(fn ($envKey) => preg_match('/^'.preg_quote($key->value(), '/').'_[0-9]+$/', $envKey) === 1);
$serviceExists = findServiceApplicationForEnvName($resource, (string) $urlFor);
// Check if FQDN already has a port set (contains ':' after the domain)
$fqdnHasPort = $serviceExists && str($serviceExists->fqdn)->contains(':') && str($serviceExists->fqdn)->afterLast(':')->isMatch('/^\d+$/');
@@ -154,3 +154,25 @@ it('fixes SERVICE_URL_TWENTY for installs where worker already has a generated d
expect($this->service->environment_variables()->where('key', 'SERVICE_URL_TWENTY')->value('value'))->toBe('https://crm.example.com')
->and($this->service->applications()->where('name', 'worker')->value('fqdn'))->toBe('http://twenty-stale.10.0.1.1.sslip.io');
});
it('treats SERVICE_FQDN_ with the same name, port and value as a copy of SERVICE_URL_', function () {
$this->service->update(['docker_compose_raw' => <<<'YAML'
services:
twenty:
image: twentycrm/twenty:v1.15
environment:
- SERVICE_URL_TWENTY_3000
worker:
image: twentycrm/twenty:v1.15
environment:
- SERVICE_FQDN_TWENTY_3000
YAML]);
$this->service->parse();
$this->service->applications()->where('name', 'twenty')->first()->update(['fqdn' => 'https://crm.example.com']);
$this->service->fresh()->parse();
expect($this->service->environment_variables()->where('key', 'SERVICE_URL_TWENTY')->value('value'))->toBe('https://crm.example.com')
->and($this->service->environment_variables()->where('key', 'SERVICE_FQDN_TWENTY_3000')->value('value'))->toBe('crm.example.com:3000')
->and($this->service->applications()->where('name', 'worker')->value('fqdn'))->toBeNull();
});