mirror of
https://github.com/tiennm99/coolify.git
synced 2026-04-17 17:21:04 +00:00
Merge branch 'next' into feat/prioritize-branch-selection
This commit is contained in:
@@ -68,10 +68,16 @@ function queue_application_deployment(Application $application, string $deployme
|
||||
]);
|
||||
|
||||
if ($no_questions_asked) {
|
||||
$deployment->update([
|
||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||
]);
|
||||
ApplicationDeploymentJob::dispatch(
|
||||
application_deployment_queue_id: $deployment->id,
|
||||
);
|
||||
} elseif (next_queuable($server_id, $application_id, $commit, $pull_request_id)) {
|
||||
$deployment->update([
|
||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||
]);
|
||||
ApplicationDeploymentJob::dispatch(
|
||||
application_deployment_queue_id: $deployment->id,
|
||||
);
|
||||
|
||||
@@ -48,6 +48,8 @@ const DATABASE_DOCKER_IMAGES = [
|
||||
'influxdb',
|
||||
'clickhouse/clickhouse-server',
|
||||
'timescaledb/timescaledb',
|
||||
'timescaledb', // Matches timescale/timescaledb
|
||||
'timescaledb-ha', // Matches timescale/timescaledb-ha
|
||||
'pgvector/pgvector',
|
||||
];
|
||||
const SPECIFIC_SERVICES = [
|
||||
@@ -56,6 +58,7 @@ const SPECIFIC_SERVICES = [
|
||||
'ghcr.io/coollabsio/minio',
|
||||
'coollabsio/minio',
|
||||
'svhd/logto',
|
||||
'dxflrs/garage',
|
||||
];
|
||||
|
||||
// Based on /etc/os-release
|
||||
|
||||
@@ -312,6 +312,36 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource)
|
||||
$LOGTO_ADMIN_ENDPOINT->value.':3002',
|
||||
]);
|
||||
break;
|
||||
case $type?->contains('garage'):
|
||||
$GARAGE_S3_API_URL = $variables->where('key', 'GARAGE_S3_API_URL')->first();
|
||||
$GARAGE_WEB_URL = $variables->where('key', 'GARAGE_WEB_URL')->first();
|
||||
$GARAGE_ADMIN_URL = $variables->where('key', 'GARAGE_ADMIN_URL')->first();
|
||||
|
||||
if (is_null($GARAGE_S3_API_URL) || is_null($GARAGE_WEB_URL) || is_null($GARAGE_ADMIN_URL)) {
|
||||
return collect([]);
|
||||
}
|
||||
|
||||
if (str($GARAGE_S3_API_URL->value ?? '')->isEmpty()) {
|
||||
$GARAGE_S3_API_URL->update([
|
||||
'value' => generateUrl(server: $server, random: 's3-'.$uuid, forceHttps: true),
|
||||
]);
|
||||
}
|
||||
if (str($GARAGE_WEB_URL->value ?? '')->isEmpty()) {
|
||||
$GARAGE_WEB_URL->update([
|
||||
'value' => generateUrl(server: $server, random: 'web-'.$uuid, forceHttps: true),
|
||||
]);
|
||||
}
|
||||
if (str($GARAGE_ADMIN_URL->value ?? '')->isEmpty()) {
|
||||
$GARAGE_ADMIN_URL->update([
|
||||
'value' => generateUrl(server: $server, random: 'admin-'.$uuid, forceHttps: true),
|
||||
]);
|
||||
}
|
||||
$payload = collect([
|
||||
$GARAGE_S3_API_URL->value.':3900',
|
||||
$GARAGE_WEB_URL->value.':3902',
|
||||
$GARAGE_ADMIN_URL->value.':3903',
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
@@ -770,10 +800,26 @@ function isDatabaseImage(?string $image = null, ?array $serviceConfig = null)
|
||||
}
|
||||
$imageName = $image->before(':');
|
||||
|
||||
// First check if it's a known database image
|
||||
// Extract base image name (ignore registry prefix)
|
||||
// Examples:
|
||||
// docker.io/library/postgres -> postgres
|
||||
// ghcr.io/postgrest/postgrest -> postgrest
|
||||
// postgres -> postgres
|
||||
// postgrest/postgrest -> postgrest
|
||||
$baseImageName = $imageName;
|
||||
if (str($imageName)->contains('/')) {
|
||||
$baseImageName = str($imageName)->afterLast('/');
|
||||
}
|
||||
|
||||
// Check if base image name exactly matches a known database image
|
||||
$isKnownDatabase = false;
|
||||
foreach (DATABASE_DOCKER_IMAGES as $database_docker_image) {
|
||||
if (str($imageName)->contains($database_docker_image)) {
|
||||
// Extract base name from database pattern for comparison
|
||||
$databaseBaseName = str($database_docker_image)->contains('/')
|
||||
? str($database_docker_image)->afterLast('/')
|
||||
: $database_docker_image;
|
||||
|
||||
if ($baseImageName == $databaseBaseName) {
|
||||
$isKnownDatabase = true;
|
||||
break;
|
||||
}
|
||||
@@ -1376,3 +1422,62 @@ function injectDockerComposeFlags(string $command, string $composeFilePath, stri
|
||||
// Replace only first occurrence to avoid modifying comments/strings/chained commands
|
||||
return preg_replace('/docker\s+compose/', $dockerComposeReplacement, $command, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject build arguments right after build-related subcommands in docker/docker compose commands.
|
||||
* This ensures build args are only applied to build operations, not to push, pull, up, etc.
|
||||
*
|
||||
* Supports:
|
||||
* - docker compose build
|
||||
* - docker buildx build
|
||||
* - docker builder build
|
||||
* - docker build (legacy)
|
||||
*
|
||||
* Examples:
|
||||
* - Input: "docker compose -f file.yml build"
|
||||
* Output: "docker compose -f file.yml build --build-arg X --build-arg Y"
|
||||
*
|
||||
* - Input: "docker buildx build --platform linux/amd64"
|
||||
* Output: "docker buildx build --build-arg X --build-arg Y --platform linux/amd64"
|
||||
*
|
||||
* - Input: "docker builder build --tag myimage:latest"
|
||||
* Output: "docker builder build --build-arg X --build-arg Y --tag myimage:latest"
|
||||
*
|
||||
* - Input: "docker compose build && docker compose push"
|
||||
* Output: "docker compose build --build-arg X --build-arg Y && docker compose push"
|
||||
*
|
||||
* - Input: "docker compose push"
|
||||
* Output: "docker compose push" (unchanged - no build command found)
|
||||
*
|
||||
* @param string $command The docker command
|
||||
* @param string $buildArgsString The build arguments to inject (e.g., "--build-arg X --build-arg Y")
|
||||
* @return string The modified command with build args injected after build subcommand
|
||||
*/
|
||||
function injectDockerComposeBuildArgs(string $command, string $buildArgsString): string
|
||||
{
|
||||
// Early return if no build args to inject
|
||||
if (empty(trim($buildArgsString))) {
|
||||
return $command;
|
||||
}
|
||||
|
||||
// Match build-related commands:
|
||||
// - ' builder build' (docker builder build)
|
||||
// - ' buildx build' (docker buildx build)
|
||||
// - ' build' (docker compose build, docker build)
|
||||
// Followed by either:
|
||||
// - whitespace (allowing service names, flags, or any valid arguments)
|
||||
// - end of string ($)
|
||||
// This regex ensures we match build subcommands, not "build" in other contexts
|
||||
// IMPORTANT: Order matters - check longer patterns first (builder build, buildx build) before ' build'
|
||||
$pattern = '/( builder build| buildx build| build)(?=\s|$)/';
|
||||
|
||||
// Replace the first occurrence of build command with build command + build-args
|
||||
$modifiedCommand = preg_replace(
|
||||
$pattern,
|
||||
'$1 '.$buildArgsString,
|
||||
$command,
|
||||
1 // Only replace first occurrence
|
||||
);
|
||||
|
||||
return $modifiedCommand ?? $command;
|
||||
}
|
||||
|
||||
@@ -358,7 +358,7 @@ function parseDockerVolumeString(string $volumeString): array
|
||||
];
|
||||
}
|
||||
|
||||
function applicationParser(Application $resource, int $pull_request_id = 0, ?int $preview_id = null): Collection
|
||||
function applicationParser(Application $resource, int $pull_request_id = 0, ?int $preview_id = null, ?string $commit = null): Collection
|
||||
{
|
||||
$uuid = data_get($resource, 'uuid');
|
||||
$compose = data_get($resource, 'docker_compose_raw');
|
||||
@@ -1324,6 +1324,20 @@ function applicationParser(Application $resource, int $pull_request_id = 0, ?int
|
||||
->values();
|
||||
|
||||
$payload['env_file'] = $envFiles;
|
||||
|
||||
// Inject commit-based image tag for services with build directive (for rollback support)
|
||||
// Only inject if service has build but no explicit image defined
|
||||
$hasBuild = data_get($service, 'build') !== null;
|
||||
$hasImage = data_get($service, 'image') !== null;
|
||||
if ($hasBuild && ! $hasImage && $commit) {
|
||||
$imageTag = str($commit)->substr(0, 128)->value();
|
||||
if ($isPullRequest) {
|
||||
$imageTag = "pr-{$pullRequestId}";
|
||||
}
|
||||
$imageRepo = "{$uuid}_{$serviceName}";
|
||||
$payload['image'] = "{$imageRepo}:{$imageTag}";
|
||||
}
|
||||
|
||||
if ($isPullRequest) {
|
||||
$serviceName = addPreviewDeploymentSuffix($serviceName, $pullRequestId);
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ function instant_remote_process_with_timeout(Collection|array $command, Server $
|
||||
);
|
||||
}
|
||||
|
||||
function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false): ?string
|
||||
function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false, ?int $timeout = null, bool $disableMultiplexing = false): ?string
|
||||
{
|
||||
$command = $command instanceof Collection ? $command->toArray() : $command;
|
||||
|
||||
@@ -126,11 +126,12 @@ function instant_remote_process(Collection|array $command, Server $server, bool
|
||||
$command = parseCommandsByLineForSudo(collect($command), $server);
|
||||
}
|
||||
$command_string = implode("\n", $command);
|
||||
$effectiveTimeout = $timeout ?? config('constants.ssh.command_timeout');
|
||||
|
||||
return \App\Helpers\SshRetryHandler::retry(
|
||||
function () use ($server, $command_string) {
|
||||
$sshCommand = SshMultiplexingHelper::generateSshCommand($server, $command_string);
|
||||
$process = Process::timeout(config('constants.ssh.command_timeout'))->run($sshCommand);
|
||||
function () use ($server, $command_string, $effectiveTimeout, $disableMultiplexing) {
|
||||
$sshCommand = SshMultiplexingHelper::generateSshCommand($server, $command_string, $disableMultiplexing);
|
||||
$process = Process::timeout($effectiveTimeout)->run($sshCommand);
|
||||
|
||||
$output = trim($process->output());
|
||||
$exitCode = $process->exitCode();
|
||||
|
||||
Reference in New Issue
Block a user