mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(proxy): accept Docker Compose merge tags in proxy configurations
Proxy port validation parsed the configuration with plain Symfony YAML, which rejects Docker Compose tags such as !reset and !override. Proxy start, restart and save then failed for these valid configurations, and automatic start after a reboot stopped silently. v4.3.23 did not block on such configurations. The parser now reads custom tags and validates the values inside them, so malformed ports are still rejected. CheckProxy refreshes the status of a running proxy before it reads the configuration, as in v4.3.23, so an unreadable configuration no longer hides a running proxy.
This commit is contained in:
@@ -53,6 +53,17 @@ class CheckProxy
|
||||
|
||||
return true;
|
||||
} else {
|
||||
$status = getContainerStatus($server, $proxyContainerName);
|
||||
if ($status === 'running') {
|
||||
$server->proxy->set('status', 'running');
|
||||
$server->save();
|
||||
|
||||
return false;
|
||||
}
|
||||
if ($server->settings->is_cloudflare_tunnel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$portsToCheck = [];
|
||||
|
||||
try {
|
||||
@@ -65,17 +76,6 @@ class CheckProxy
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$status = getContainerStatus($server, $proxyContainerName);
|
||||
if ($status === 'running') {
|
||||
$server->proxy->set('status', 'running');
|
||||
$server->save();
|
||||
|
||||
return false;
|
||||
}
|
||||
if ($server->settings->is_cloudflare_tunnel) {
|
||||
return false;
|
||||
}
|
||||
if (count($portsToCheck) === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Services;
|
||||
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use Symfony\Component\Yaml\Tag\TaggedValue;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class ProxyPortParser
|
||||
@@ -31,12 +32,14 @@ class ProxyPortParser
|
||||
* a port invalid: a variable with a default is checked as its default, and a port that still
|
||||
* depends on a variable (or whose default is not a valid port) is accepted but not returned.
|
||||
*
|
||||
* Docker Compose merge tags such as `!reset` and `!override` are accepted; their values are validated.
|
||||
*
|
||||
* @return list<int>
|
||||
*/
|
||||
public static function fromConfiguration(string $configuration): array
|
||||
{
|
||||
try {
|
||||
$parsed = Yaml::parse($configuration);
|
||||
$parsed = self::withoutTags(Yaml::parse($configuration, Yaml::PARSE_CUSTOM_TAGS));
|
||||
} catch (ParseException $exception) {
|
||||
throw new \InvalidArgumentException('The proxy configuration must contain valid YAML.', previous: $exception);
|
||||
}
|
||||
@@ -70,6 +73,19 @@ class ProxyPortParser
|
||||
return array_values(array_unique($ports));
|
||||
}
|
||||
|
||||
private static function withoutTags(mixed $value): mixed
|
||||
{
|
||||
if ($value instanceof TaggedValue) {
|
||||
return self::withoutTags($value->getValue());
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return array_map(self::withoutTags(...), $value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private static function publishedPort(mixed $configuredPort): ?int
|
||||
{
|
||||
if (is_string($configuredPort) && self::containsVariable($configuredPort)) {
|
||||
|
||||
@@ -4,6 +4,7 @@ use App\Actions\Proxy\CheckProxy;
|
||||
use App\Actions\Proxy\SaveProxyConfiguration;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
@@ -44,7 +45,19 @@ it('does not execute remote commands for a malformed legacy stored port', functi
|
||||
$this->server->save();
|
||||
|
||||
expect(CheckProxy::run($this->server))->toBeFalse();
|
||||
Process::assertNothingRan();
|
||||
Process::assertDidntRun(fn ($process): bool => str_contains((string) $process->command, '$(id)'));
|
||||
});
|
||||
|
||||
it('refreshes the status of a running proxy even when its stored configuration is invalid', function () {
|
||||
Process::fake([
|
||||
'*' => Process::result(output: json_encode(['State' => ['Status' => 'running']])),
|
||||
]);
|
||||
$this->server->private_key_id = PrivateKey::factory()->create(['team_id' => $this->server->team_id])->id;
|
||||
$this->server->proxy->last_saved_proxy_configuration = "services:\n traefik:\n image: traefik:v3.7\n ports:\n - '$(id):80'\n";
|
||||
$this->server->save();
|
||||
|
||||
expect(CheckProxy::run($this->server->fresh()))->toBeFalse()
|
||||
->and($this->server->fresh()->proxy->status)->toBe('running');
|
||||
});
|
||||
|
||||
it('builds port checks only from normalized integer ports', function () {
|
||||
|
||||
@@ -113,3 +113,17 @@ it('rejects invalid proxy port collection shapes', function (mixed $ports) {
|
||||
'null' => [null],
|
||||
'boolean' => [true],
|
||||
]);
|
||||
|
||||
it('accepts Docker Compose merge tags in proxy configurations', function (string $configuration, array $expected) {
|
||||
expect(ProxyPortParser::fromConfiguration($configuration))->toBe($expected);
|
||||
})->with([
|
||||
'override ports' => ["services:\n traefik:\n ports: !override\n - '80:80'\n - '443:443'\n", [80, 443]],
|
||||
'reset ports' => ["services:\n traefik:\n ports: !reset []\n", []],
|
||||
'reset labels next to ports' => ["services:\n caddy:\n labels: !reset []\n ports: ['8080:80']\n", [8080]],
|
||||
'override a long syntax port' => ["services:\n traefik:\n ports: !override\n - target: 80\n published: !!str 8080\n", [8080]],
|
||||
]);
|
||||
|
||||
it('still rejects malformed ports inside Docker Compose merge tags', function () {
|
||||
expect(fn () => ProxyPortParser::fromConfiguration("services:\n traefik:\n ports: !override\n - '\$(id):80'\n"))
|
||||
->toThrow(InvalidArgumentException::class);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user