mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
Merge remote-tracking branch 'origin/main' into next
This commit is contained in:
@@ -154,12 +154,7 @@ class ApplicationsController extends Controller
|
||||
$application->settings?->makeHidden(['id', 'application_id', 'created_at', 'updated_at']);
|
||||
}
|
||||
|
||||
$payload = serializeApiResponse($application);
|
||||
if ($application instanceof Application && filled(data_get($payload, 'docker_compose'))) {
|
||||
$payload['docker_compose'] = redactLockedValuesInGeneratedCompose($payload['docker_compose'], $application);
|
||||
}
|
||||
|
||||
return $payload;
|
||||
return redactLockedApiResourceValues($application, serializeApiResponse($application));
|
||||
}
|
||||
|
||||
private function applicationSettingsFromRequest(Request $request): array
|
||||
@@ -5087,20 +5082,25 @@ class ApplicationsController extends Controller
|
||||
], 422);
|
||||
}
|
||||
|
||||
// Validate ports_mappings
|
||||
if ($request->has('ports_mappings')) {
|
||||
$ports = [];
|
||||
// Validate that each host binding (IP, port, protocol) of ports_mappings is unique
|
||||
if (is_string($request->ports_mappings)) {
|
||||
$hostBindings = [];
|
||||
foreach (explode(',', $request->ports_mappings) as $portMapping) {
|
||||
$port = explode(':', $portMapping);
|
||||
if (in_array($port[0], $ports)) {
|
||||
// The container port is after the last ":", an empty host port makes Docker pick a free port
|
||||
$hostBinding = preg_replace('#/(tcp|udp|sctp)#', '', substr($portMapping, 0, (int) strrpos($portMapping, ':')));
|
||||
if ($hostBinding === '' || str_ends_with($hostBinding, ':')) {
|
||||
continue;
|
||||
}
|
||||
$protocol = preg_match('#/(udp|sctp)#', $portMapping, $matches) === 1 ? $matches[1] : 'tcp';
|
||||
if (in_array("{$hostBinding}/{$protocol}", $hostBindings, true)) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
'ports_mappings' => 'The first number before : should be unique between mappings.',
|
||||
'ports_mappings' => 'Each host IP, port, and protocol can only be used once.',
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
$ports[] = $port[0];
|
||||
$hostBindings[] = "{$hostBinding}/{$protocol}";
|
||||
}
|
||||
}
|
||||
// Validate custom_labels
|
||||
|
||||
@@ -258,19 +258,7 @@ class DatabasesController extends Controller
|
||||
$this->hideNestedServerSecrets($database, $loadNestedServerSecrets);
|
||||
}
|
||||
|
||||
$payload = serializeApiResponse($database);
|
||||
|
||||
// A locked password variable is never shown again, also not inside the connection URLs.
|
||||
if (request()->attributes->get('can_read_sensitive', false) === true
|
||||
&& method_exists($database, 'isCredentialLocked') && $database->isCredentialLocked()) {
|
||||
foreach (['internal_db_url', 'external_db_url'] as $key) {
|
||||
if ($payload->has($key)) {
|
||||
$payload->put($key, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $payload;
|
||||
return redactLockedApiResourceValues($database, serializeApiResponse($database));
|
||||
}
|
||||
|
||||
private function hideNestedServerSecrets(Model $model, bool $loadRelations = false): void
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Models\Project;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
@@ -168,13 +169,16 @@ class ProjectController extends Controller
|
||||
return response()->json(['message' => 'Environment not found.'], 404);
|
||||
}
|
||||
// Redis connection URLs read REDIS_PASSWORD and REDIS_USERNAME; load the variables once, not per database.
|
||||
$environment = $environment->load(['applications', 'postgresqls', 'redis.runtime_environment_variables', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
|
||||
collect(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services'])
|
||||
->flatMap(fn (string $relation) => $environment->{$relation})
|
||||
->each(fn ($resource) => exposeSensitiveFields($resource));
|
||||
$environment->redis->each(fn ($redis) => $redis->makeHidden('runtime_environment_variables'));
|
||||
$relations = ['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services'];
|
||||
$environment = $environment->load([...$relations, 'redis.runtime_environment_variables']);
|
||||
$payload = $environment->withoutRelations()->toArray();
|
||||
foreach ($relations as $relation) {
|
||||
$payload[$relation] = $environment->{$relation}
|
||||
->map(fn ($resource) => Arr::except(serializeApiResource($resource), 'runtime_environment_variables'))
|
||||
->all();
|
||||
}
|
||||
|
||||
return response()->json(serializeApiResponse($environment));
|
||||
return response()->json(serializeApiResponse($payload));
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
|
||||
@@ -69,8 +69,7 @@ class ResourcesController extends Controller
|
||||
}
|
||||
$resources = $resources->flatten();
|
||||
$resources = $resources->map(function ($resource) {
|
||||
exposeSensitiveFields($resource);
|
||||
$payload = $resource->toArray();
|
||||
$payload = serializeApiResource($resource);
|
||||
$payload['status'] = $resource->status;
|
||||
$payload['type'] = $resource->type();
|
||||
|
||||
|
||||
@@ -78,12 +78,7 @@ class ServicesController extends Controller
|
||||
$service->makeHidden(['value', 'real_value']);
|
||||
}
|
||||
|
||||
$payload = serializeApiResponse($service);
|
||||
if ($service instanceof Service && filled(data_get($payload, 'docker_compose'))) {
|
||||
$payload['docker_compose'] = redactLockedValuesInGeneratedCompose($payload['docker_compose'], $service);
|
||||
}
|
||||
|
||||
return $payload;
|
||||
return redactLockedApiResourceValues($service, serializeApiResponse($service));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -508,7 +508,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
|
||||
$this->build_server = $buildServers->random();
|
||||
$this->application_deployment_queue->build_server_id = $this->build_server->id;
|
||||
// Saved now: the cancel action looks for the helper container on this server.
|
||||
$this->application_deployment_queue->update(['build_server_id' => $this->build_server->id]);
|
||||
$this->application_deployment_queue->addLogEntry("Found a suitable build server ({$this->build_server->name}).");
|
||||
$this->use_build_server = true;
|
||||
}
|
||||
@@ -5247,8 +5248,9 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$timeout = $this->application->settings->deploymentStopGracePeriodSeconds();
|
||||
|
||||
if ($skipRemove) {
|
||||
// The helper container (started with --rm) must also stop when the deployment was cancelled.
|
||||
$this->execute_remote_command(
|
||||
[dockerStopCommand($timeout, escapeshellarg($containerName), $this->server), 'hidden' => true, 'ignore_errors' => true]
|
||||
[dockerStopCommand($timeout, escapeshellarg($containerName), $this->server), 'hidden' => true, 'ignore_errors' => true, 'skip_cancellation_check' => true]
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
|
||||
+2
-20
@@ -1136,14 +1136,7 @@ class Server extends BaseModel
|
||||
0 => 'http',
|
||||
],
|
||||
'service' => 'coolify-reverb',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
|
||||
],
|
||||
'coolify-reverb-api' => [
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
],
|
||||
'service' => 'coolify-reverb',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/apps`)",
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/app/`)",
|
||||
],
|
||||
'coolify-terminal-ws' => [
|
||||
'entryPoints' => [
|
||||
@@ -1206,15 +1199,7 @@ class Server extends BaseModel
|
||||
0 => 'https',
|
||||
],
|
||||
'service' => 'coolify-reverb',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
|
||||
'tls' => $tls,
|
||||
];
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-reverb-api-https'] = [
|
||||
'entryPoints' => [
|
||||
0 => 'https',
|
||||
],
|
||||
'service' => 'coolify-reverb',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/apps`)",
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/app/`)",
|
||||
'tls' => $tls,
|
||||
];
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-terminal-wss'] = [
|
||||
@@ -1256,9 +1241,6 @@ $siteAddress {
|
||||
handle /app/* {
|
||||
reverse_proxy coolify:6001
|
||||
}
|
||||
handle /apps* {
|
||||
reverse_proxy coolify:6001
|
||||
}
|
||||
handle /terminal/ws {
|
||||
reverse_proxy coolify:6002
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ trait ExecuteRemoteCommand
|
||||
$command_hidden = data_get($single_command, 'command_hidden', false);
|
||||
$skip_command_log = data_get($single_command, 'skip_command_log', false);
|
||||
$input = data_get($single_command, 'input');
|
||||
$skip_cancellation_check = data_get($single_command, 'skip_cancellation_check', false);
|
||||
$this->save = data_get($single_command, 'save');
|
||||
if ($this->server->isNonRoot()) {
|
||||
if (str($command)->startsWith('docker exec')) {
|
||||
@@ -96,8 +97,8 @@ trait ExecuteRemoteCommand
|
||||
}
|
||||
}
|
||||
|
||||
// Check for cancellation before executing commands
|
||||
if (isset($this->application_deployment_queue)) {
|
||||
// Check for cancellation before executing commands. Cleanup commands must also run after a cancel.
|
||||
if (! $skip_cancellation_check && isset($this->application_deployment_queue)) {
|
||||
$this->application_deployment_queue->refresh();
|
||||
if ($this->application_deployment_queue->status === ApplicationDeploymentStatus::CANCELLED_BY_USER->value) {
|
||||
throw new \RuntimeException('Deployment cancelled by user', 69420);
|
||||
@@ -126,9 +127,11 @@ trait ExecuteRemoteCommand
|
||||
$this->addRetryLogEntry($attempt, $maxRetries, $delay, $errorMessage);
|
||||
|
||||
// Check for cancellation during retry wait
|
||||
$this->application_deployment_queue->refresh();
|
||||
if ($this->application_deployment_queue->status === ApplicationDeploymentStatus::CANCELLED_BY_USER->value) {
|
||||
throw new \RuntimeException('Deployment cancelled by user during retry', 69420);
|
||||
if (! $skip_cancellation_check) {
|
||||
$this->application_deployment_queue->refresh();
|
||||
if ($this->application_deployment_queue->status === ApplicationDeploymentStatus::CANCELLED_BY_USER->value) {
|
||||
throw new \RuntimeException('Deployment cancelled by user during retry', 69420);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user