wip: new deployment jobs
This commit is contained in:
@@ -1,8 +1,15 @@
|
||||
<?php
|
||||
|
||||
use App\Jobs\ApplicationDeployDockerImageJob;
|
||||
use App\Jobs\ApplicationDeploymentJob;
|
||||
use App\Jobs\ApplicationDeploySimpleDockerfileJob;
|
||||
use App\Jobs\ApplicationRestartJob;
|
||||
use App\Jobs\MultipleApplicationDeploymentJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\Server;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
function queue_application_deployment(int $application_id, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null)
|
||||
{
|
||||
@@ -29,17 +36,305 @@ function queue_application_deployment(int $application_id, string $deployment_uu
|
||||
if ($running_deployments->count() > 0) {
|
||||
return;
|
||||
}
|
||||
// New deployment
|
||||
// dispatchDeploymentJob($deployment->id);
|
||||
dispatch(new ApplicationDeploymentJob(
|
||||
application_deployment_queue_id: $deployment->id,
|
||||
))->onConnection('long-running')->onQueue('long-running');
|
||||
|
||||
}
|
||||
|
||||
function queue_next_deployment(Application $application)
|
||||
{
|
||||
$next_found = ApplicationDeploymentQueue::where('application_id', $application->id)->where('status', 'queued')->first();
|
||||
if ($next_found) {
|
||||
// New deployment
|
||||
// dispatchDeploymentJob($next_found->id);
|
||||
dispatch(new ApplicationDeploymentJob(
|
||||
application_deployment_queue_id: $next_found->id,
|
||||
))->onConnection('long-running')->onQueue('long-running');
|
||||
|
||||
}
|
||||
}
|
||||
function dispatchDeploymentJob($id)
|
||||
{
|
||||
$applicationQueue = ApplicationDeploymentQueue::find($id);
|
||||
$application = Application::find($applicationQueue->application_id);
|
||||
|
||||
$isRestartOnly = data_get($applicationQueue, 'restart_only');
|
||||
$isSimpleDockerFile = data_get($application, 'dockerfile');
|
||||
$isDockerImage = data_get($application, 'build_pack') === 'dockerimage';
|
||||
|
||||
if ($isRestartOnly) {
|
||||
ApplicationRestartJob::dispatch(applicationDeploymentQueueId: $id)->onConnection('long-running')->onQueue('long-running');
|
||||
} else if ($isSimpleDockerFile) {
|
||||
ApplicationDeploySimpleDockerfileJob::dispatch(applicationDeploymentQueueId: $id)->onConnection('long-running')->onQueue('long-running');
|
||||
} else if ($isDockerImage) {
|
||||
ApplicationDeployDockerImageJob::dispatch(applicationDeploymentQueueId: $id)->onConnection('long-running')->onQueue('long-running');
|
||||
} else {
|
||||
throw new Exception('Unknown build pack');
|
||||
}
|
||||
}
|
||||
|
||||
// Deployment things
|
||||
function generateHostIpMapping(Server $server, string $network)
|
||||
{
|
||||
// Generate custom host<->ip hostnames
|
||||
$allContainers = instant_remote_process(["docker network inspect {$network} -f '{{json .Containers}}' "], $server);
|
||||
$allContainers = format_docker_command_output_to_json($allContainers);
|
||||
$ips = collect([]);
|
||||
if (count($allContainers) > 0) {
|
||||
$allContainers = $allContainers[0];
|
||||
foreach ($allContainers as $container) {
|
||||
$containerName = data_get($container, 'Name');
|
||||
if ($containerName === 'coolify-proxy') {
|
||||
continue;
|
||||
}
|
||||
$containerIp = data_get($container, 'IPv4Address');
|
||||
if ($containerName && $containerIp) {
|
||||
$containerIp = str($containerIp)->before('/');
|
||||
$ips->put($containerName, $containerIp->value());
|
||||
}
|
||||
}
|
||||
}
|
||||
return $ips->map(function ($ip, $name) {
|
||||
return "--add-host $name:$ip";
|
||||
})->implode(' ');
|
||||
}
|
||||
|
||||
function generateBaseDir(string $deplyomentUuid)
|
||||
{
|
||||
return "/artifacts/$deplyomentUuid";
|
||||
}
|
||||
function generateWorkdir(string $deplyomentUuid, Application $application)
|
||||
{
|
||||
return generateBaseDir($deplyomentUuid) . rtrim($application->base_directory, '/');
|
||||
}
|
||||
|
||||
function prepareHelperContainer(Server $server, string $network, string $deploymentUuid)
|
||||
{
|
||||
$basedir = generateBaseDir($deploymentUuid);
|
||||
$helperImage = config('coolify.helper_image');
|
||||
|
||||
$serverUserHomeDir = instant_remote_process(["echo \$HOME"], $server);
|
||||
$dockerConfigFileExists = instant_remote_process(["test -f {$serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $server);
|
||||
|
||||
$commands = collect([]);
|
||||
if ($dockerConfigFileExists === 'OK') {
|
||||
$commands->push([
|
||||
"command" => "docker run -d --network $network -v /:/host --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage",
|
||||
"hidden" => true,
|
||||
]);
|
||||
} else {
|
||||
$commands->push([
|
||||
"command" => "docker run -d --network {$network} -v /:/host --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}",
|
||||
"hidden" => true,
|
||||
]);
|
||||
}
|
||||
$commands->push([
|
||||
"command" => executeInDocker($deploymentUuid, "mkdir -p {$basedir}"),
|
||||
"hidden" => true,
|
||||
]);
|
||||
return $commands;
|
||||
}
|
||||
|
||||
function generateComposeFile(string $deploymentUuid, Server $server, string $network, Application $application, string $containerName, string $imageName, ?ApplicationPreview $preview = null, int $pullRequestId = 0)
|
||||
{
|
||||
$ports = $application->settings->is_static ? [80] : $application->ports_exposes_array;
|
||||
$workDir = generateWorkdir($deploymentUuid, $application);
|
||||
$persistent_storages = generateLocalPersistentVolumes($application, $pullRequestId);
|
||||
$volume_names = generateLocalPersistentVolumesOnlyVolumeNames($application, $pullRequestId);
|
||||
$environment_variables = generateEnvironmentVariables($application, $ports, $pullRequestId);
|
||||
|
||||
if (data_get($application, 'custom_labels')) {
|
||||
$labels = collect(str($application->custom_labels)->explode(','));
|
||||
$labels = $labels->filter(function ($value, $key) {
|
||||
return !str($value)->startsWith('coolify.');
|
||||
});
|
||||
$application->custom_labels = $labels->implode(',');
|
||||
$application->save();
|
||||
} else {
|
||||
$labels = collect(generateLabelsApplication($application, $preview));
|
||||
}
|
||||
if ($pullRequestId !== 0) {
|
||||
$labels = collect(generateLabelsApplication($application, $preview));
|
||||
}
|
||||
$labels = $labels->merge(defaultLabels($application->id, $application->uuid, 0))->toArray();
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$containerName => [
|
||||
'image' => $imageName,
|
||||
'container_name' => $containerName,
|
||||
'restart' => RESTART_MODE,
|
||||
'environment' => $environment_variables,
|
||||
'labels' => $labels,
|
||||
'expose' => $ports,
|
||||
'networks' => [
|
||||
$network,
|
||||
],
|
||||
'mem_limit' => $application->limits_memory,
|
||||
'memswap_limit' => $application->limits_memory_swap,
|
||||
'mem_swappiness' => $application->limits_memory_swappiness,
|
||||
'mem_reservation' => $application->limits_memory_reservation,
|
||||
'cpus' => (int) $application->limits_cpus,
|
||||
'cpuset' => $application->limits_cpuset,
|
||||
'cpu_shares' => $application->limits_cpu_shares,
|
||||
]
|
||||
],
|
||||
'networks' => [
|
||||
$network => [
|
||||
'external' => true,
|
||||
'name' => $network,
|
||||
'attachable' => true
|
||||
]
|
||||
]
|
||||
];
|
||||
if ($server->isLogDrainEnabled() && $application->isLogDrainEnabled()) {
|
||||
$docker_compose['services'][$containerName]['logging'] = [
|
||||
'driver' => 'fluentd',
|
||||
'options' => [
|
||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
||||
'fluentd-async' => "true",
|
||||
'fluentd-sub-second-precision' => "true",
|
||||
]
|
||||
];
|
||||
}
|
||||
if ($application->settings->is_gpu_enabled) {
|
||||
ray('asd');
|
||||
$docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'] = [
|
||||
[
|
||||
'driver' => data_get($application, 'settings.gpu_driver', 'nvidia'),
|
||||
'capabilities' => ['gpu'],
|
||||
'options' => data_get($application, 'settings.gpu_options', [])
|
||||
]
|
||||
];
|
||||
if (data_get($application, 'settings.gpu_count')) {
|
||||
$count = data_get($application, 'settings.gpu_count');
|
||||
if ($count === 'all') {
|
||||
$docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'][0]['count'] = $count;
|
||||
} else {
|
||||
$docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'][0]['count'] = (int) $count;
|
||||
}
|
||||
} else if (data_get($application, 'settings.gpu_device_ids')) {
|
||||
$docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'][0]['ids'] = data_get($application, 'settings.gpu_device_ids');
|
||||
}
|
||||
}
|
||||
if ($application->isHealthcheckDisabled()) {
|
||||
data_forget($docker_compose, 'services.' . $containerName . '.healthcheck');
|
||||
}
|
||||
if (count($application->ports_mappings_array) > 0 && $pullRequestId === 0) {
|
||||
$docker_compose['services'][$containerName]['ports'] = $application->ports_mappings_array;
|
||||
}
|
||||
if (count($persistent_storages) > 0) {
|
||||
$docker_compose['services'][$containerName]['volumes'] = $persistent_storages;
|
||||
}
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||
$docker_compose_base64 = base64_encode($docker_compose);
|
||||
$commands = collect([]);
|
||||
$commands->push([
|
||||
"command" => executeInDocker($deploymentUuid, "echo '{$docker_compose_base64}' | base64 -d > {$workDir}/docker-compose.yml"),
|
||||
"hidden" => true,
|
||||
]);
|
||||
return $commands;
|
||||
}
|
||||
function generateLocalPersistentVolumes(Application $application, int $pullRequestId = 0)
|
||||
{
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($application->persistentStorages as $persistentStorage) {
|
||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||
if ($pullRequestId !== 0) {
|
||||
$volume_name = $volume_name . '-pr-' . $pullRequestId;
|
||||
}
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
return $local_persistent_volumes;
|
||||
}
|
||||
|
||||
function generateLocalPersistentVolumesOnlyVolumeNames(Application $application, int $pullRequestId = 0)
|
||||
{
|
||||
$local_persistent_volumes_names = [];
|
||||
foreach ($application->persistentStorages as $persistentStorage) {
|
||||
if ($persistentStorage->host_path) {
|
||||
continue;
|
||||
}
|
||||
$name = $persistentStorage->name;
|
||||
|
||||
if ($pullRequestId !== 0) {
|
||||
$name = $name . '-pr-' . $pullRequestId;
|
||||
}
|
||||
|
||||
$local_persistent_volumes_names[$name] = [
|
||||
'name' => $name,
|
||||
'external' => false,
|
||||
];
|
||||
}
|
||||
return $local_persistent_volumes_names;
|
||||
}
|
||||
function generateEnvironmentVariables(Application $application, $ports, int $pullRequestId = 0)
|
||||
{
|
||||
$environment_variables = collect();
|
||||
// ray('Generate Environment Variables')->green();
|
||||
if ($pullRequestId === 0) {
|
||||
// ray($this->application->runtime_environment_variables)->green();
|
||||
foreach ($application->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
}
|
||||
foreach ($application->nixpacks_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
}
|
||||
} else {
|
||||
// ray($this->application->runtime_environment_variables_preview)->green();
|
||||
foreach ($application->runtime_environment_variables_preview as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
}
|
||||
foreach ($application->nixpacks_environment_variables_preview as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
}
|
||||
}
|
||||
// Add PORT if not exists, use the first port as default
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('PORT'))->isEmpty()) {
|
||||
$environment_variables->push("PORT={$ports[0]}");
|
||||
}
|
||||
return $environment_variables->all();
|
||||
}
|
||||
|
||||
function rollingUpdate(Application $application, string $deploymentUuid)
|
||||
{
|
||||
$commands = collect([]);
|
||||
$workDir = generateWorkdir($deploymentUuid, $application);
|
||||
if (count($application->ports_mappings_array) > 0) {
|
||||
// $this->execute_remote_command(
|
||||
// [
|
||||
// "echo '\n----------------------------------------'",
|
||||
// ],
|
||||
// ["echo -n 'Application has ports mapped to the host system, rolling update is not supported.'"],
|
||||
// );
|
||||
// $this->stop_running_container(force: true);
|
||||
// $this->start_by_compose_file();
|
||||
} else {
|
||||
$commands->push(
|
||||
[
|
||||
"command" => "echo '\n----------------------------------------'"
|
||||
],
|
||||
[
|
||||
"command" => "echo -n 'Rolling update started.'"
|
||||
]
|
||||
);
|
||||
if ($application->build_pack === 'dockerimage') {
|
||||
$commands->push(
|
||||
["echo -n 'Pulling latest images from the registry.'"],
|
||||
[executeInDocker($deploymentUuid, "docker compose --project-directory {$workDir} pull"), "hidden" => true],
|
||||
[executeInDocker($deploymentUuid, "docker compose --project-directory {$workDir} up --build -d"), "hidden" => true],
|
||||
);
|
||||
} else {
|
||||
$commands->push(
|
||||
[executeInDocker($deploymentUuid, "docker compose --project-directory {$workDir} up --build -d"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
return $commands;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user