mirror of
https://github.com/tiennm99/coolify.git
synced 2026-04-17 17:21:04 +00:00
Merge branch 'next' into next
This commit is contained in:
@@ -1069,9 +1069,9 @@ function validateComposeFile(string $compose, int $server_id): string|Throwable
|
||||
}
|
||||
}
|
||||
}
|
||||
$compose_content = Yaml::dump($yaml_compose);
|
||||
transfer_file_to_server($compose_content, "/tmp/{$uuid}.yml", $server);
|
||||
$base64_compose = base64_encode(Yaml::dump($yaml_compose));
|
||||
instant_remote_process([
|
||||
"echo {$base64_compose} | base64 -d | tee /tmp/{$uuid}.yml > /dev/null",
|
||||
"chmod 600 /tmp/{$uuid}.yml",
|
||||
"docker compose -f /tmp/{$uuid}.yml config --no-interpolate --no-path-resolution -q",
|
||||
"rm /tmp/{$uuid}.yml",
|
||||
@@ -1093,11 +1093,11 @@ function getContainerLogs(Server $server, string $container_id, int $lines = 100
|
||||
{
|
||||
if ($server->isSwarm()) {
|
||||
$output = instant_remote_process([
|
||||
"docker service logs -n {$lines} {$container_id}",
|
||||
"docker service logs -n {$lines} {$container_id} 2>&1",
|
||||
], $server);
|
||||
} else {
|
||||
$output = instant_remote_process([
|
||||
"docker logs -n {$lines} {$container_id}",
|
||||
"docker logs -n {$lines} {$container_id} 2>&1",
|
||||
], $server);
|
||||
}
|
||||
|
||||
@@ -1105,7 +1105,6 @@ function getContainerLogs(Server $server, string $container_id, int $lines = 100
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function escapeEnvVariables($value)
|
||||
{
|
||||
$search = ['\\', "\r", "\t", "\x0", '"', "'"];
|
||||
|
||||
@@ -135,7 +135,13 @@ function getPermissionsPath(GithubApp $source)
|
||||
|
||||
function loadRepositoryByPage(GithubApp $source, string $token, int $page)
|
||||
{
|
||||
$response = Http::withToken($token)->get("{$source->api_url}/installation/repositories?per_page=100&page={$page}");
|
||||
$response = Http::GitHub($source->api_url, $token)
|
||||
->timeout(20)
|
||||
->retry(3, 200, throw: false)
|
||||
->get('/installation/repositories', [
|
||||
'per_page' => 100,
|
||||
'page' => $page,
|
||||
]);
|
||||
$json = $response->json();
|
||||
if ($response->status() !== 200) {
|
||||
return [
|
||||
|
||||
@@ -385,21 +385,34 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
|
||||
'is_preview' => false,
|
||||
]);
|
||||
if ($resource->build_pack === 'dockercompose') {
|
||||
$domains = collect(json_decode(data_get($resource, 'docker_compose_domains'))) ?? collect([]);
|
||||
$domainExists = data_get($domains->get($fqdnFor), 'domain');
|
||||
$envExists = $resource->environment_variables()->where('key', $key->value())->first();
|
||||
if (str($domainExists)->replace('http://', '')->replace('https://', '')->value() !== $envExists->value) {
|
||||
$envExists->update([
|
||||
'value' => $url,
|
||||
]);
|
||||
// Check if a service with this name actually exists
|
||||
$serviceExists = false;
|
||||
foreach ($services as $serviceName => $service) {
|
||||
$transformedServiceName = str($serviceName)->replace('-', '_')->replace('.', '_')->value();
|
||||
if ($transformedServiceName === $fqdnFor) {
|
||||
$serviceExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_null($domainExists)) {
|
||||
// Put URL in the domains array instead of FQDN
|
||||
$domains->put((string) $fqdnFor, [
|
||||
'domain' => $url,
|
||||
]);
|
||||
$resource->docker_compose_domains = $domains->toJson();
|
||||
$resource->save();
|
||||
|
||||
// Only add domain if the service exists
|
||||
if ($serviceExists) {
|
||||
$domains = collect(json_decode(data_get($resource, 'docker_compose_domains'))) ?? collect([]);
|
||||
$domainExists = data_get($domains->get($fqdnFor), 'domain');
|
||||
$envExists = $resource->environment_variables()->where('key', $key->value())->first();
|
||||
if (str($domainExists)->replace('http://', '')->replace('https://', '')->value() !== $envExists->value) {
|
||||
$envExists->update([
|
||||
'value' => $url,
|
||||
]);
|
||||
}
|
||||
if (is_null($domainExists)) {
|
||||
// Put URL in the domains array instead of FQDN
|
||||
$domains->put((string) $fqdnFor, [
|
||||
'domain' => $url,
|
||||
]);
|
||||
$resource->docker_compose_domains = $domains->toJson();
|
||||
$resource->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($command->value() === 'URL') {
|
||||
@@ -418,20 +431,33 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
|
||||
'is_preview' => false,
|
||||
]);
|
||||
if ($resource->build_pack === 'dockercompose') {
|
||||
$domains = collect(json_decode(data_get($resource, 'docker_compose_domains'))) ?? collect([]);
|
||||
$domainExists = data_get($domains->get($urlFor), 'domain');
|
||||
$envExists = $resource->environment_variables()->where('key', $key->value())->first();
|
||||
if ($domainExists !== $envExists->value) {
|
||||
$envExists->update([
|
||||
'value' => $url,
|
||||
]);
|
||||
// Check if a service with this name actually exists
|
||||
$serviceExists = false;
|
||||
foreach ($services as $serviceName => $service) {
|
||||
$transformedServiceName = str($serviceName)->replace('-', '_')->replace('.', '_')->value();
|
||||
if ($transformedServiceName === $urlFor) {
|
||||
$serviceExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_null($domainExists)) {
|
||||
$domains->put((string) $urlFor, [
|
||||
'domain' => $url,
|
||||
]);
|
||||
$resource->docker_compose_domains = $domains->toJson();
|
||||
$resource->save();
|
||||
|
||||
// Only add domain if the service exists
|
||||
if ($serviceExists) {
|
||||
$domains = collect(json_decode(data_get($resource, 'docker_compose_domains'))) ?? collect([]);
|
||||
$domainExists = data_get($domains->get($urlFor), 'domain');
|
||||
$envExists = $resource->environment_variables()->where('key', $key->value())->first();
|
||||
if ($domainExists !== $envExists->value) {
|
||||
$envExists->update([
|
||||
'value' => $url,
|
||||
]);
|
||||
}
|
||||
if (is_null($domainExists)) {
|
||||
$domains->put((string) $urlFor, [
|
||||
'domain' => $url,
|
||||
]);
|
||||
$resource->docker_compose_domains = $domains->toJson();
|
||||
$resource->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -910,7 +936,7 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
|
||||
$preview = $resource->previews()->find($preview_id);
|
||||
$docker_compose_domains = collect(json_decode(data_get($preview, 'docker_compose_domains')));
|
||||
if ($docker_compose_domains->count() > 0) {
|
||||
$found_fqdn = data_get($docker_compose_domains, "$serviceName.domain");
|
||||
$found_fqdn = data_get($docker_compose_domains, "$changedServiceName.domain");
|
||||
if ($found_fqdn) {
|
||||
$fqdns = collect($found_fqdn);
|
||||
} else {
|
||||
|
||||
@@ -29,31 +29,11 @@ function remote_process(
|
||||
$type = $type ?? ActivityTypes::INLINE->value;
|
||||
$command = $command instanceof Collection ? $command->toArray() : $command;
|
||||
|
||||
// Process commands and handle file transfers
|
||||
$processed_commands = [];
|
||||
foreach ($command as $cmd) {
|
||||
if (is_array($cmd) && isset($cmd['transfer_file'])) {
|
||||
// Handle file transfer command
|
||||
$transfer_data = $cmd['transfer_file'];
|
||||
$content = $transfer_data['content'];
|
||||
$destination = $transfer_data['destination'];
|
||||
|
||||
// Execute file transfer immediately
|
||||
transfer_file_to_server($content, $destination, $server, ! $ignore_errors);
|
||||
|
||||
// Add a comment to the command log for visibility
|
||||
$processed_commands[] = "# File transferred via SCP: $destination";
|
||||
} else {
|
||||
// Regular string command
|
||||
$processed_commands[] = $cmd;
|
||||
}
|
||||
}
|
||||
|
||||
if ($server->isNonRoot()) {
|
||||
$processed_commands = parseCommandsByLineForSudo(collect($processed_commands), $server);
|
||||
$command = parseCommandsByLineForSudo(collect($command), $server);
|
||||
}
|
||||
|
||||
$command_string = implode("\n", $processed_commands);
|
||||
$command_string = implode("\n", $command);
|
||||
|
||||
if (Auth::check()) {
|
||||
$teams = Auth::user()->teams->pluck('id');
|
||||
@@ -104,64 +84,6 @@ function instant_scp(string $source, string $dest, Server $server, $throwError =
|
||||
);
|
||||
}
|
||||
|
||||
function transfer_file_to_container(string $content, string $container_path, string $deployment_uuid, Server $server, bool $throwError = true): ?string
|
||||
{
|
||||
$temp_file = tempnam(sys_get_temp_dir(), 'coolify_env_');
|
||||
|
||||
try {
|
||||
// Write content to temporary file
|
||||
file_put_contents($temp_file, $content);
|
||||
|
||||
// Generate unique filename for server transfer
|
||||
$server_temp_file = '/tmp/coolify_env_'.uniqid().'_'.$deployment_uuid;
|
||||
|
||||
// Transfer file to server
|
||||
instant_scp($temp_file, $server_temp_file, $server, $throwError);
|
||||
|
||||
// Ensure parent directory exists in container, then copy file
|
||||
$parent_dir = dirname($container_path);
|
||||
$commands = [];
|
||||
if ($parent_dir !== '.' && $parent_dir !== '/') {
|
||||
$commands[] = executeInDocker($deployment_uuid, "mkdir -p \"$parent_dir\"");
|
||||
}
|
||||
$commands[] = "docker cp $server_temp_file $deployment_uuid:$container_path";
|
||||
$commands[] = "rm -f $server_temp_file"; // Cleanup server temp file
|
||||
|
||||
return instant_remote_process_with_timeout($commands, $server, $throwError);
|
||||
|
||||
} finally {
|
||||
// Always cleanup local temp file
|
||||
if (file_exists($temp_file)) {
|
||||
unlink($temp_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function transfer_file_to_server(string $content, string $server_path, Server $server, bool $throwError = true): ?string
|
||||
{
|
||||
$temp_file = tempnam(sys_get_temp_dir(), 'coolify_env_');
|
||||
|
||||
try {
|
||||
// Write content to temporary file
|
||||
file_put_contents($temp_file, $content);
|
||||
|
||||
// Ensure parent directory exists on server
|
||||
$parent_dir = dirname($server_path);
|
||||
if ($parent_dir !== '.' && $parent_dir !== '/') {
|
||||
instant_remote_process_with_timeout(["mkdir -p \"$parent_dir\""], $server, $throwError);
|
||||
}
|
||||
|
||||
// Transfer file directly to server destination
|
||||
return instant_scp($temp_file, $server_path, $server, $throwError);
|
||||
|
||||
} finally {
|
||||
// Always cleanup local temp file
|
||||
if (file_exists($temp_file)) {
|
||||
unlink($temp_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function instant_remote_process_with_timeout(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false): ?string
|
||||
{
|
||||
$command = $command instanceof Collection ? $command->toArray() : $command;
|
||||
@@ -200,30 +122,10 @@ function instant_remote_process(Collection|array $command, Server $server, bool
|
||||
{
|
||||
$command = $command instanceof Collection ? $command->toArray() : $command;
|
||||
|
||||
// Process commands and handle file transfers
|
||||
$processed_commands = [];
|
||||
foreach ($command as $cmd) {
|
||||
if (is_array($cmd) && isset($cmd['transfer_file'])) {
|
||||
// Handle file transfer command
|
||||
$transfer_data = $cmd['transfer_file'];
|
||||
$content = $transfer_data['content'];
|
||||
$destination = $transfer_data['destination'];
|
||||
|
||||
// Execute file transfer immediately
|
||||
transfer_file_to_server($content, $destination, $server, $throwError);
|
||||
|
||||
// Add a comment to the command log for visibility
|
||||
$processed_commands[] = "# File transferred via SCP: $destination";
|
||||
} else {
|
||||
// Regular string command
|
||||
$processed_commands[] = $cmd;
|
||||
}
|
||||
}
|
||||
|
||||
if ($server->isNonRoot() && ! $no_sudo) {
|
||||
$processed_commands = parseCommandsByLineForSudo(collect($processed_commands), $server);
|
||||
$command = parseCommandsByLineForSudo(collect($command), $server);
|
||||
}
|
||||
$command_string = implode("\n", $processed_commands);
|
||||
$command_string = implode("\n", $command);
|
||||
|
||||
return \App\Helpers\SshRetryHandler::retry(
|
||||
function () use ($server, $command_string) {
|
||||
|
||||
@@ -69,11 +69,12 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Appli
|
||||
$fileVolume->content = $content;
|
||||
$fileVolume->is_directory = false;
|
||||
$fileVolume->save();
|
||||
$content = base64_encode($content);
|
||||
$dir = str($fileLocation)->dirname();
|
||||
instant_remote_process([
|
||||
"mkdir -p $dir",
|
||||
"echo '$content' | base64 -d | tee $fileLocation",
|
||||
], $server);
|
||||
transfer_file_to_server($content, $fileLocation, $server);
|
||||
} elseif ($isFile === 'NOK' && $isDir === 'NOK' && $fileVolume->is_directory && $isInit) {
|
||||
// Does not exists (no dir or file), flagged as directory, is init
|
||||
$fileVolume->content = null;
|
||||
|
||||
@@ -634,10 +634,14 @@ function getTopLevelNetworks(Service|Application $resource)
|
||||
$definedNetwork = collect([$resource->uuid]);
|
||||
$services = collect($services)->map(function ($service, $_) use ($topLevelNetworks, $definedNetwork) {
|
||||
$serviceNetworks = collect(data_get($service, 'networks', []));
|
||||
$hasHostNetworkMode = data_get($service, 'network_mode') === 'host' ? true : false;
|
||||
$networkMode = data_get($service, 'network_mode');
|
||||
|
||||
// Only add 'networks' key if 'network_mode' is not 'host'
|
||||
if (! $hasHostNetworkMode) {
|
||||
$hasValidNetworkMode =
|
||||
$networkMode === 'host' ||
|
||||
(is_string($networkMode) && (str_starts_with($networkMode, 'service:') || str_starts_with($networkMode, 'container:')));
|
||||
|
||||
// Only add 'networks' key if 'network_mode' is not 'host' or does not start with 'service:' or 'container:'
|
||||
if (! $hasValidNetworkMode) {
|
||||
// Collect/create/update networks
|
||||
if ($serviceNetworks->count() > 0) {
|
||||
foreach ($serviceNetworks as $networkName => $networkDetails) {
|
||||
@@ -1125,30 +1129,77 @@ function get_public_ips()
|
||||
function isAnyDeploymentInprogress()
|
||||
{
|
||||
$runningJobs = ApplicationDeploymentQueue::where('horizon_job_worker', gethostname())->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value)->get();
|
||||
$basicDetails = $runningJobs->map(function ($job) {
|
||||
return [
|
||||
'id' => $job->id,
|
||||
'created_at' => $job->created_at,
|
||||
'application_id' => $job->application_id,
|
||||
'server_id' => $job->server_id,
|
||||
'horizon_job_id' => $job->horizon_job_id,
|
||||
'status' => $job->status,
|
||||
];
|
||||
});
|
||||
echo 'Running jobs: '.json_encode($basicDetails)."\n";
|
||||
|
||||
if ($runningJobs->isEmpty()) {
|
||||
echo "No deployments in progress.\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
$horizonJobIds = [];
|
||||
$deploymentDetails = [];
|
||||
|
||||
foreach ($runningJobs as $runningJob) {
|
||||
$horizonJobStatus = getJobStatus($runningJob->horizon_job_id);
|
||||
if ($horizonJobStatus === 'unknown' || $horizonJobStatus === 'reserved') {
|
||||
$horizonJobIds[] = $runningJob->horizon_job_id;
|
||||
|
||||
// Get application and team information
|
||||
$application = Application::find($runningJob->application_id);
|
||||
$teamMembers = [];
|
||||
$deploymentUrl = '';
|
||||
|
||||
if ($application) {
|
||||
// Get team members through the application's project
|
||||
$team = $application->team();
|
||||
if ($team) {
|
||||
$teamMembers = $team->members()->pluck('email')->toArray();
|
||||
}
|
||||
|
||||
// Construct the full deployment URL
|
||||
if ($runningJob->deployment_url) {
|
||||
$baseUrl = base_url();
|
||||
$deploymentUrl = $baseUrl.$runningJob->deployment_url;
|
||||
}
|
||||
}
|
||||
|
||||
$deploymentDetails[] = [
|
||||
'id' => $runningJob->id,
|
||||
'application_name' => $runningJob->application_name ?? 'Unknown',
|
||||
'server_name' => $runningJob->server_name ?? 'Unknown',
|
||||
'deployment_url' => $deploymentUrl,
|
||||
'team_members' => $teamMembers,
|
||||
'created_at' => $runningJob->created_at->format('Y-m-d H:i:s'),
|
||||
'horizon_job_id' => $runningJob->horizon_job_id,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (count($horizonJobIds) === 0) {
|
||||
echo "No deployments in progress.\n";
|
||||
echo "No active deployments in progress (all jobs completed or failed).\n";
|
||||
exit(0);
|
||||
}
|
||||
$horizonJobIds = collect($horizonJobIds)->unique()->toArray();
|
||||
echo 'There are '.count($horizonJobIds)." deployments in progress.\n";
|
||||
|
||||
// Display enhanced deployment information
|
||||
echo "\n=== Running Deployments ===\n";
|
||||
echo 'Total active deployments: '.count($horizonJobIds)."\n\n";
|
||||
|
||||
foreach ($deploymentDetails as $index => $deployment) {
|
||||
echo 'Deployment #'.($index + 1).":\n";
|
||||
echo ' Application: '.$deployment['application_name']."\n";
|
||||
echo ' Server: '.$deployment['server_name']."\n";
|
||||
echo ' Started: '.$deployment['created_at']."\n";
|
||||
if ($deployment['deployment_url']) {
|
||||
echo ' URL: '.$deployment['deployment_url']."\n";
|
||||
}
|
||||
if (! empty($deployment['team_members'])) {
|
||||
echo ' Team members: '.implode(', ', $deployment['team_members'])."\n";
|
||||
} else {
|
||||
echo " Team members: No team members found\n";
|
||||
}
|
||||
echo ' Horizon Job ID: '.$deployment['horizon_job_id']."\n";
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -1225,7 +1276,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$serviceNetworks = collect(data_get($service, 'networks', []));
|
||||
$serviceVariables = collect(data_get($service, 'environment', []));
|
||||
$serviceLabels = collect(data_get($service, 'labels', []));
|
||||
$hasHostNetworkMode = data_get($service, 'network_mode') === 'host' ? true : false;
|
||||
$networkMode = data_get($service, 'network_mode');
|
||||
|
||||
$hasValidNetworkMode =
|
||||
$networkMode === 'host' ||
|
||||
(is_string($networkMode) && (str_starts_with($networkMode, 'service:') || str_starts_with($networkMode, 'container:')));
|
||||
|
||||
if ($serviceLabels->count() > 0) {
|
||||
$removedLabels = collect([]);
|
||||
$serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) {
|
||||
@@ -1336,7 +1392,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$savedService->ports = $collectedPorts->implode(',');
|
||||
$savedService->save();
|
||||
|
||||
if (! $hasHostNetworkMode) {
|
||||
if (! $hasValidNetworkMode) {
|
||||
// Add Coolify specific networks
|
||||
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
|
||||
return $value == $definedNetwork;
|
||||
|
||||
Reference in New Issue
Block a user