mirror of
https://github.com/tiennm99/coolify.git
synced 2026-04-17 17:21:04 +00:00
feat(proxy): enhance proxy configuration regeneration by extracting custom commands
- Added a new function to extract custom proxy commands from existing Traefik configurations before regenerating the proxy configuration. - Updated the proxy configuration generation logic to include these custom commands, ensuring they are preserved during regeneration. - Introduced unit tests to validate the extraction of custom commands and handle various scenarios, including invalid YAML and different proxy types.
This commit is contained in:
@@ -33,7 +33,13 @@ class GetProxyConfiguration
|
|||||||
// 1. Force regenerate is requested
|
// 1. Force regenerate is requested
|
||||||
// 2. Configuration file doesn't exist or is empty
|
// 2. Configuration file doesn't exist or is empty
|
||||||
if ($forceRegenerate || empty(trim($proxy_configuration ?? ''))) {
|
if ($forceRegenerate || empty(trim($proxy_configuration ?? ''))) {
|
||||||
$proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value();
|
// Extract custom commands from existing config before regenerating
|
||||||
|
$custom_commands = [];
|
||||||
|
if (! empty(trim($proxy_configuration ?? ''))) {
|
||||||
|
$custom_commands = extractCustomProxyCommands($server, $proxy_configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
$proxy_configuration = str(generateDefaultProxyConfiguration($server, $custom_commands))->trim()->value();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($proxy_configuration)) {
|
if (empty($proxy_configuration)) {
|
||||||
|
|||||||
@@ -108,7 +108,63 @@ function connectProxyToNetworks(Server $server)
|
|||||||
|
|
||||||
return $commands->flatten();
|
return $commands->flatten();
|
||||||
}
|
}
|
||||||
function generate_default_proxy_configuration(Server $server)
|
function extractCustomProxyCommands(Server $server, string $existing_config): array
|
||||||
|
{
|
||||||
|
$custom_commands = [];
|
||||||
|
$proxy_type = $server->proxyType();
|
||||||
|
|
||||||
|
if ($proxy_type !== ProxyTypes::TRAEFIK->value || empty($existing_config)) {
|
||||||
|
return $custom_commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$yaml = Yaml::parse($existing_config);
|
||||||
|
$existing_commands = data_get($yaml, 'services.traefik.command', []);
|
||||||
|
|
||||||
|
if (empty($existing_commands)) {
|
||||||
|
return $custom_commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define default commands that Coolify generates
|
||||||
|
$default_command_prefixes = [
|
||||||
|
'--ping=',
|
||||||
|
'--api.',
|
||||||
|
'--entrypoints.http.address=',
|
||||||
|
'--entrypoints.https.address=',
|
||||||
|
'--entrypoints.http.http.encodequerysemicolons=',
|
||||||
|
'--entryPoints.http.http2.maxConcurrentStreams=',
|
||||||
|
'--entrypoints.https.http.encodequerysemicolons=',
|
||||||
|
'--entryPoints.https.http2.maxConcurrentStreams=',
|
||||||
|
'--entrypoints.https.http3',
|
||||||
|
'--providers.file.',
|
||||||
|
'--certificatesresolvers.',
|
||||||
|
'--providers.docker',
|
||||||
|
'--providers.swarm',
|
||||||
|
'--log.level=',
|
||||||
|
'--accesslog.',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Extract commands that don't match default prefixes (these are custom)
|
||||||
|
foreach ($existing_commands as $command) {
|
||||||
|
$is_default = false;
|
||||||
|
foreach ($default_command_prefixes as $prefix) {
|
||||||
|
if (str_starts_with($command, $prefix)) {
|
||||||
|
$is_default = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! $is_default) {
|
||||||
|
$custom_commands[] = $command;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// If we can't parse the config, return empty array
|
||||||
|
// Silently fail to avoid breaking the proxy regeneration
|
||||||
|
}
|
||||||
|
|
||||||
|
return $custom_commands;
|
||||||
|
}
|
||||||
|
function generateDefaultProxyConfiguration(Server $server, array $custom_commands = [])
|
||||||
{
|
{
|
||||||
$proxy_path = $server->proxyPath();
|
$proxy_path = $server->proxyPath();
|
||||||
$proxy_type = $server->proxyType();
|
$proxy_type = $server->proxyType();
|
||||||
@@ -228,6 +284,13 @@ function generate_default_proxy_configuration(Server $server)
|
|||||||
$config['services']['traefik']['command'][] = '--providers.docker=true';
|
$config['services']['traefik']['command'][] = '--providers.docker=true';
|
||||||
$config['services']['traefik']['command'][] = '--providers.docker.exposedbydefault=false';
|
$config['services']['traefik']['command'][] = '--providers.docker.exposedbydefault=false';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Append custom commands (e.g., trustedIPs for Cloudflare)
|
||||||
|
if (! empty($custom_commands)) {
|
||||||
|
foreach ($custom_commands as $custom_command) {
|
||||||
|
$config['services']['traefik']['command'][] = $custom_command;
|
||||||
|
}
|
||||||
|
}
|
||||||
} elseif ($proxy_type === 'CADDY') {
|
} elseif ($proxy_type === 'CADDY') {
|
||||||
$config = [
|
$config = [
|
||||||
'networks' => $array_of_networks->toArray(),
|
'networks' => $array_of_networks->toArray(),
|
||||||
|
|||||||
137
tests/Unit/ProxyCustomCommandsTest.php
Normal file
137
tests/Unit/ProxyCustomCommandsTest.php
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
it('extracts custom proxy commands from existing traefik configuration', function () {
|
||||||
|
// Create a sample config with custom trustedIPs commands
|
||||||
|
$existingConfig = [
|
||||||
|
'services' => [
|
||||||
|
'traefik' => [
|
||||||
|
'command' => [
|
||||||
|
'--ping=true',
|
||||||
|
'--api.dashboard=true',
|
||||||
|
'--entrypoints.http.address=:80',
|
||||||
|
'--entrypoints.https.address=:443',
|
||||||
|
'--entrypoints.http.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22',
|
||||||
|
'--entrypoints.https.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22',
|
||||||
|
'--providers.docker=true',
|
||||||
|
'--providers.docker.exposedbydefault=false',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$yamlConfig = Yaml::dump($existingConfig);
|
||||||
|
|
||||||
|
// Mock a server with Traefik proxy type
|
||||||
|
$server = Mockery::mock('App\Models\Server');
|
||||||
|
$server->shouldReceive('proxyType')->andReturn(ProxyTypes::TRAEFIK->value);
|
||||||
|
|
||||||
|
$customCommands = extractCustomProxyCommands($server, $yamlConfig);
|
||||||
|
|
||||||
|
expect($customCommands)
|
||||||
|
->toBeArray()
|
||||||
|
->toHaveCount(2)
|
||||||
|
->toContain('--entrypoints.http.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22')
|
||||||
|
->toContain('--entrypoints.https.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns empty array when only default commands exist', function () {
|
||||||
|
// Config with only default commands
|
||||||
|
$existingConfig = [
|
||||||
|
'services' => [
|
||||||
|
'traefik' => [
|
||||||
|
'command' => [
|
||||||
|
'--ping=true',
|
||||||
|
'--api.dashboard=true',
|
||||||
|
'--entrypoints.http.address=:80',
|
||||||
|
'--entrypoints.https.address=:443',
|
||||||
|
'--providers.docker=true',
|
||||||
|
'--providers.docker.exposedbydefault=false',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$yamlConfig = Yaml::dump($existingConfig);
|
||||||
|
|
||||||
|
$server = Mockery::mock('App\Models\Server');
|
||||||
|
$server->shouldReceive('proxyType')->andReturn(ProxyTypes::TRAEFIK->value);
|
||||||
|
|
||||||
|
$customCommands = extractCustomProxyCommands($server, $yamlConfig);
|
||||||
|
|
||||||
|
expect($customCommands)->toBeArray()->toBeEmpty();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles invalid yaml gracefully', function () {
|
||||||
|
$invalidYaml = 'this is not: valid: yaml::: content';
|
||||||
|
|
||||||
|
$server = Mockery::mock('App\Models\Server');
|
||||||
|
$server->shouldReceive('proxyType')->andReturn(ProxyTypes::TRAEFIK->value);
|
||||||
|
|
||||||
|
$customCommands = extractCustomProxyCommands($server, $invalidYaml);
|
||||||
|
|
||||||
|
expect($customCommands)->toBeArray()->toBeEmpty();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns empty array for caddy proxy type', function () {
|
||||||
|
$existingConfig = [
|
||||||
|
'services' => [
|
||||||
|
'caddy' => [
|
||||||
|
'environment' => ['SOME_VAR=value'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$yamlConfig = Yaml::dump($existingConfig);
|
||||||
|
|
||||||
|
$server = Mockery::mock('App\Models\Server');
|
||||||
|
$server->shouldReceive('proxyType')->andReturn(ProxyTypes::CADDY->value);
|
||||||
|
|
||||||
|
$customCommands = extractCustomProxyCommands($server, $yamlConfig);
|
||||||
|
|
||||||
|
expect($customCommands)->toBeArray()->toBeEmpty();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns empty array when config is empty', function () {
|
||||||
|
$server = Mockery::mock('App\Models\Server');
|
||||||
|
$server->shouldReceive('proxyType')->andReturn(ProxyTypes::TRAEFIK->value);
|
||||||
|
|
||||||
|
$customCommands = extractCustomProxyCommands($server, '');
|
||||||
|
|
||||||
|
expect($customCommands)->toBeArray()->toBeEmpty();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly identifies multiple custom command types', function () {
|
||||||
|
$existingConfig = [
|
||||||
|
'services' => [
|
||||||
|
'traefik' => [
|
||||||
|
'command' => [
|
||||||
|
'--ping=true',
|
||||||
|
'--api.dashboard=true',
|
||||||
|
'--entrypoints.http.forwardedHeaders.trustedIPs=173.245.48.0/20',
|
||||||
|
'--entrypoints.https.forwardedHeaders.trustedIPs=173.245.48.0/20',
|
||||||
|
'--entrypoints.http.forwardedHeaders.insecure=true',
|
||||||
|
'--metrics.prometheus=true',
|
||||||
|
'--providers.docker=true',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$yamlConfig = Yaml::dump($existingConfig);
|
||||||
|
|
||||||
|
$server = Mockery::mock('App\Models\Server');
|
||||||
|
$server->shouldReceive('proxyType')->andReturn(ProxyTypes::TRAEFIK->value);
|
||||||
|
|
||||||
|
$customCommands = extractCustomProxyCommands($server, $yamlConfig);
|
||||||
|
|
||||||
|
expect($customCommands)
|
||||||
|
->toBeArray()
|
||||||
|
->toHaveCount(4)
|
||||||
|
->toContain('--entrypoints.http.forwardedHeaders.trustedIPs=173.245.48.0/20')
|
||||||
|
->toContain('--entrypoints.https.forwardedHeaders.trustedIPs=173.245.48.0/20')
|
||||||
|
->toContain('--entrypoints.http.forwardedHeaders.insecure=true')
|
||||||
|
->toContain('--metrics.prometheus=true');
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user