Merge branch 'next' into env-vars

This commit is contained in:
Andras Bacsai
2024-08-29 14:35:04 +02:00
committed by GitHub
33 changed files with 1037 additions and 1560 deletions

View File

@@ -19,14 +19,19 @@ class StopService
ray('Stopping service: '.$service->name);
$applications = $service->applications()->get();
foreach ($applications as $application) {
instant_remote_process(command: ["docker stop --time=30 {$application->name}-{$service->uuid}"], server: $server, throwError: false);
if ($applications->count() < 6) {
instant_remote_process(command: ["docker stop --time=10 {$application->name}-{$service->uuid}"], server: $server, throwError: false);
}
instant_remote_process(command: ["docker rm {$application->name}-{$service->uuid}"], server: $server, throwError: false);
instant_remote_process(command: ["docker rm -f {$application->name}-{$service->uuid}"], server: $server, throwError: false);
$application->update(['status' => 'exited']);
}
$dbs = $service->databases()->get();
foreach ($dbs as $db) {
instant_remote_process(command: ["docker stop --time=30 {$db->name}-{$service->uuid}"], server: $server, throwError: false);
if ($dbs->count() < 6) {
instant_remote_process(command: ["docker stop --time=10 {$db->name}-{$service->uuid}"], server: $server, throwError: false);
}
instant_remote_process(command: ["docker rm {$db->name}-{$service->uuid}"], server: $server, throwError: false);
instant_remote_process(command: ["docker rm -f {$db->name}-{$service->uuid}"], server: $server, throwError: false);
$db->update(['status' => 'exited']);

View File

@@ -3,6 +3,7 @@
namespace App\Console\Commands;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\ScheduledTask;
use App\Models\Service;
use App\Models\ServiceApplication;
@@ -42,6 +43,17 @@ class CleanupStuckedResources extends Command
} catch (\Throwable $e) {
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
}
try {
$applicationsPreviews = ApplicationPreview::get();
foreach ($applicationsPreviews as $applicationPreview) {
if (! data_get($applicationPreview, 'application')) {
echo "Deleting stuck application preview: {$applicationPreview->uuid}\n";
$applicationPreview->delete();
}
}
} catch (\Throwable $e) {
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
}
try {
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
foreach ($postgresqls as $postgresql) {

View File

@@ -443,7 +443,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$fileStorage->deleteStorageOnServer();
$fileStorage->saveStorageOnServer();
}
}
}
}
@@ -455,7 +454,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$yaml = $composeFile = $this->application->docker_compose_raw;
$this->save_environment_variables();
} else {
$composeFile = $this->application->oldParser(pull_request_id: $this->pull_request_id, preview_id: data_get($this->preview, 'id'));
$composeFile = $this->application->parse(pull_request_id: $this->pull_request_id, preview_id: data_get($this->preview, 'id'));
$this->save_environment_variables();
if (! is_null($this->env_filename)) {
$services = collect($composeFile['services']);
@@ -476,7 +475,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
$this->docker_compose_base64 = base64_encode($yaml);
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"), 'hidden' => true,
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"),
'hidden' => true,
]);
// Build new container to limit downtime.
$this->application_deployment_queue->addLogEntry('Pulling & building required images.');
@@ -506,9 +506,13 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
// TODO
} else {
$this->execute_remote_command([
"docker network inspect '{$networkId}' >/dev/null 2>&1 || docker network create --attachable '{$networkId}' >/dev/null || true", 'hidden' => true, 'ignore_errors' => true,
"docker network inspect '{$networkId}' >/dev/null 2>&1 || docker network create --attachable '{$networkId}' >/dev/null || true",
'hidden' => true,
'ignore_errors' => true,
], [
"docker network connect {$networkId} coolify-proxy || true", 'hidden' => true, 'ignore_errors' => true,
"docker network connect {$networkId} coolify-proxy || true",
'hidden' => true,
'ignore_errors' => true,
]);
}
@@ -561,7 +565,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
);
$this->write_deployment_configurations();
}
}
}
@@ -744,7 +747,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->application_deployment_queue->addLogEntry("Pushing image to docker registry ({$this->production_image_name}).");
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"), 'hidden' => true,
executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"),
'hidden' => true,
],
);
if ($this->application->docker_registry_image_tag) {
@@ -752,10 +756,14 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->application_deployment_queue->addLogEntry("Tagging and pushing image with {$this->application->docker_registry_image_tag} tag.");
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true,
executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"),
'ignore_errors' => true,
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true,
executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"),
'ignore_errors' => true,
'hidden' => true,
],
);
}
@@ -852,14 +860,20 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private function check_image_locally_or_remotely()
{
$this->execute_remote_command([
"docker images -q {$this->production_image_name} 2>/dev/null", 'hidden' => true, 'save' => 'local_image_found',
"docker images -q {$this->production_image_name} 2>/dev/null",
'hidden' => true,
'save' => 'local_image_found',
]);
if (str($this->saved_outputs->get('local_image_found'))->isEmpty() && $this->application->docker_registry_image_name) {
$this->execute_remote_command([
"docker pull {$this->production_image_name} 2>/dev/null", 'ignore_errors' => true, 'hidden' => true,
"docker pull {$this->production_image_name} 2>/dev/null",
'ignore_errors' => true,
'hidden' => true,
]);
$this->execute_remote_command([
"docker images -q {$this->production_image_name} 2>/dev/null", 'hidden' => true, 'save' => 'local_image_found',
"docker images -q {$this->production_image_name} 2>/dev/null",
'hidden' => true,
'save' => 'local_image_found',
]);
}
}
@@ -892,12 +906,14 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_FQDN')->isEmpty()) {
$envs->push("COOLIFY_FQDN={$this->preview->fqdn}");
$envs->push("COOLIFY_DOMAIN_URL={$this->preview->fqdn}");
}
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_URL')->isEmpty()) {
$url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', '');
$envs->push("COOLIFY_URL={$url}");
$envs->push("COOLIFY_DOMAIN_FQDN={$url}");
}
if ($this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$envs->push("COOLIFY_BRANCH={$local_branch}");
}
@@ -942,13 +958,21 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
}
if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) {
$envs->push("COOLIFY_FQDN={$this->application->fqdn}");
if ($this->application->compose_parsing_version === '3') {
$envs->push("COOLIFY_URL={$this->application->fqdn}");
} else {
$envs->push("COOLIFY_FQDN={$this->application->fqdn}");
}
}
if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) {
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
$envs->push("COOLIFY_URL={$url}");
if ($this->application->compose_parsing_version === '3') {
$envs->push("COOLIFY_FQDN={$url}");
} else {
$envs->push("COOLIFY_URL={$url}");
}
}
if ($this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$envs->push("COOLIFY_BRANCH={$local_branch}");
}
@@ -1417,7 +1441,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
$this->execute_remote_command(
[
$importCommands, 'hidden' => true,
$importCommands,
'hidden' => true,
]
);
$this->create_workdir();
@@ -1627,7 +1652,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
// Check for custom HEALTHCHECK
if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), 'hidden' => true, 'save' => 'dockerfile_from_repo', 'ignore_errors' => true,
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"),
'hidden' => true,
'save' => 'dockerfile_from_repo',
'ignore_errors' => true,
]);
$dockerfile = collect(str($this->saved_outputs->get('dockerfile_from_repo'))->trim()->explode("\n"));
$this->application->parseHealthcheckFromDockerfile($dockerfile);
@@ -1893,7 +1921,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->application_deployment_queue->addLogEntry("Pulling latest image ($image) from the registry.");
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "docker pull {$image}"), 'hidden' => true,
executeInDocker($this->deployment_uuid, "docker pull {$image}"),
'hidden' => true,
]
);
}
@@ -1943,12 +1972,14 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]);
if ($this->force_rebuild) {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true,
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"),
'hidden' => true,
]);
$build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}";
} else {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true,
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"),
'hidden' => true,
]);
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}";
}
@@ -1956,10 +1987,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$base64_build_command = base64_encode($build_command);
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true,
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true,
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
'hidden' => true,
]
);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]);
@@ -1973,10 +2006,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true,
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true,
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
'hidden' => true,
]
);
}
@@ -2013,10 +2048,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d | tee {$this->workdir}/nginx.conf > /dev/null"),
],
[
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true,
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true,
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
'hidden' => true,
]
);
} else {
@@ -2030,10 +2067,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$base64_build_command = base64_encode($build_command);
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true,
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true,
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
'hidden' => true,
]
);
} else {
@@ -2042,22 +2081,26 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]);
if ($this->force_rebuild) {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true,
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"),
'hidden' => true,
]);
$build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
} else {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true,
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"),
'hidden' => true,
]);
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
}
$base64_build_command = base64_encode($build_command);
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true,
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true,
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
'hidden' => true,
]
);
$this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]);
@@ -2071,10 +2114,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true,
executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"),
'hidden' => true,
],
[
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true,
executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'),
'hidden' => true,
]
);
}
@@ -2100,7 +2145,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->execute_remote_command(
["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true]
);
}
private function stop_running_container(bool $force = false)
@@ -2178,7 +2222,9 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
private function add_build_env_variables_to_dockerfile()
{
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), 'hidden' => true, 'save' => 'dockerfile',
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"),
'hidden' => true,
'save' => 'dockerfile',
]);
$dockerfile = collect(str($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
if ($this->pull_request_id === 0) {
@@ -2223,7 +2269,8 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$exec = "docker exec {$containerName} {$cmd}";
$this->execute_remote_command(
[
'command' => $exec, 'hidden' => true,
'command' => $exec,
'hidden' => true,
],
);
@@ -2250,7 +2297,9 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
try {
$this->execute_remote_command(
[
'command' => $exec, 'hidden' => true, 'save' => 'post-deployment-command-output',
'command' => $exec,
'hidden' => true,
'save' => 'post-deployment-command-output',
],
);
} catch (Exception $e) {

View File

@@ -50,15 +50,6 @@ class Dashboard extends Component
])->sortBy('id')->groupBy('server_name')->toArray();
}
// public function getIptables()
// {
// $servers = Server::ownedByCurrentTeam()->get();
// foreach ($servers as $server) {
// checkRequiredCommands($server);
// $iptables = instant_remote_process(['docker run --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c "iptables -L -n | jc --iptables"'], $server);
// ray($iptables);
// }
// }
public function render()
{
return view('livewire.dashboard');

View File

@@ -68,7 +68,7 @@ class Advanced extends Component
if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->oldRawParser();
} else {
$this->application->oldParser();
$this->application->parse();
}
$this->application->settings->save();
$this->dispatch('success', 'Settings saved.');

View File

@@ -4,7 +4,6 @@ namespace App\Livewire\Project\Application;
use App\Models\Application;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@@ -132,7 +131,7 @@ class General extends Component
public function mount()
{
try {
$this->parsedServices = $this->application->oldParser();
$this->parsedServices = $this->application->parse();
if (is_null($this->parsedServices) || empty($this->parsedServices)) {
$this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');
@@ -197,7 +196,7 @@ class General extends Component
// Must reload the application to get the latest database changes
// Why? Not sure, but it works.
$this->application->refresh();
// $this->application->refresh();
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit);
if (is_null($this->parsedServices)) {
@@ -205,7 +204,7 @@ class General extends Component
return;
}
$this->application->oldParser();
$this->application->parse();
$this->dispatch('success', 'Docker compose file loaded.');
$this->dispatch('compose_loaded');
$this->dispatch('refreshStorages');

View File

@@ -79,8 +79,15 @@ class Previews extends Component
return;
}
$fqdn = generateFqdn($this->application->destination->server, $this->application->uuid);
if ($this->application->build_pack === 'dockercompose') {
$preview->generate_preview_fqdn_compose();
$this->application->refresh();
$this->dispatch('success', 'Domain generated.');
return;
}
$fqdn = generateFqdn($this->application->destination->server, $this->application->uuid);
$url = Url::fromString($fqdn);
$template = $this->application->preview_url_template;
$host = $url->getHost();

View File

@@ -91,18 +91,16 @@ class Create extends Component
$oneClickDotEnvs->each(function ($value) use ($service) {
$key = str()->before($value, '=');
$value = str(str()->after($value, '='));
$generatedValue = $value;
if ($value->contains('SERVICE_')) {
$command = $value->after('SERVICE_')->beforeLast('_');
$generatedValue = generateEnvValue($command->value(), $service);
if ($value) {
EnvironmentVariable::create([
'key' => $key,
'value' => $value,
'service_id' => $service->id,
'is_build_time' => false,
'is_preview' => false,
]);
}
EnvironmentVariable::create([
'key' => $key,
'value' => $generatedValue,
'service_id' => $service->id,
'is_build_time' => false,
'is_preview' => false,
]);
});
}
$service->parse(isNew: true);

View File

@@ -76,6 +76,12 @@ class Configuration extends Component
{
try {
GetContainersStatus::run($this->service->server);
$this->service->applications->each(function ($application) {
$application->refresh();
});
$this->service->databases->each(function ($database) {
$database->refresh();
});
$this->dispatch('$refresh');
} catch (\Exception $e) {
return handleError($e, $this);

View File

@@ -26,7 +26,7 @@ class All extends Component
$this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name'));
} elseif ($this->resource->type() == 'application') {
if ($this->resource->build_pack === 'dockercompose') {
$parsed = $this->resource->oldParser();
$parsed = $this->resource->parse();
$containers = collect(data_get($parsed, 'services'))->keys();
$this->containerNames = $containers;
} else {

View File

@@ -3,8 +3,6 @@
namespace App\Models;
use App\Enums\ApplicationDeploymentStatus;
use App\Enums\ProxyTypes;
use App\Jobs\ServerFilesFromServerJob;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -104,6 +102,8 @@ class Application extends BaseModel
{
use SoftDeletes;
private static $parserVersion = '3';
protected $guarded = [];
protected $appends = ['server_status'];
@@ -127,7 +127,7 @@ class Application extends BaseModel
ApplicationSetting::create([
'application_id' => $application->id,
]);
$application->compose_parsing_version = '2';
$application->compose_parsing_version = self::$parserVersion;
$application->save();
});
static::forceDeleting(function ($application) {
@@ -140,6 +140,7 @@ class Application extends BaseModel
$task->delete();
}
$application->tags()->detach();
$application->previews()->delete();
});
}
@@ -1085,544 +1086,10 @@ class Application extends BaseModel
instant_remote_process($commands, $this->destination->server, false);
}
public function newParser(int $pull_request_id = 0, ?int $preview_id = null)
{
$pullRequestId = $pull_request_id;
$isPullRequest = $pullRequestId == 0 ? false : true;
$uuid = data_get($this, 'uuid');
$server = data_get($this, 'destination.server');
$compose = data_get($this, 'docker_compose_raw');
try {
$yaml = Yaml::parse($compose);
} catch (\Exception $e) {
return;
}
$services = data_get($yaml, 'services', collect([]));
$topLevel = collect([
'volumes' => collect(data_get($yaml, 'volumes', [])),
'networks' => collect(data_get($yaml, 'networks', [])),
'configs' => collect(data_get($yaml, 'configs', [])),
'secrets' => collect(data_get($yaml, 'secrets', [])),
]);
// If there are predefined volumes, make sure they are not null
if ($topLevel->get('volumes')->count() > 0) {
$temp = collect([]);
foreach ($topLevel['volumes'] as $volumeName => $volume) {
if (is_null($volume)) {
continue;
}
$temp->put($volumeName, $volume);
}
$topLevel['volumes'] = $temp;
}
// Get the base docker network
$baseNetwork = collect([$uuid]);
if ($isPullRequest) {
$baseNetwork = collect(["{$uuid}-{$pullRequestId}"]);
}
$parsedServices = collect([]);
$fileStorages = $this->fileStorages();
// Let's loop through the services
foreach ($services as $serviceName => $service) {
$image = data_get_str($service, 'image');
$restart = data_get_str($service, 'restart', RESTART_MODE);
$logging = data_get($service, 'logging');
if ($server->isLogDrainEnabled() && $this->isLogDrainEnabled()) {
$logging = generate_fluentd_configuration();
}
$volumes = collect(data_get($service, 'volumes', []));
$networks = collect(data_get($service, 'networks', []));
$depends_on = collect(data_get($service, 'depends_on', []));
$labels = collect(data_get($service, 'labels', []));
$environment = collect(data_get($service, 'environment', []));
$buildArgs = collect(data_get($service, 'build.args', []));
$environment = $environment->merge($buildArgs);
$baseName = generateApplicationContainerName(
application: $this,
pull_request_id: $pullRequestId
);
$containerName = "$serviceName-$baseName";
$volumesParsed = collect([]);
if ($volumes->count() > 0) {
foreach ($volumes as $index => $volume) {
$type = null;
$source = null;
$target = null;
$content = null;
$isDirectory = false;
if (is_string($volume)) {
$source = str($volume)->before(':');
$target = str($volume)->after(':')->beforeLast(':');
$foundConfig = $fileStorages->whereMountPath($target)->first();
if (sourceIsLocal($source)) {
$type = str('bind');
if ($foundConfig) {
$contentNotNull_temp = data_get($foundConfig, 'content');
if ($contentNotNull_temp) {
$content = $contentNotNull_temp;
}
$isDirectory = data_get($foundConfig, 'is_directory');
} else {
// By default, we cannot determine if the bind is a directory or not, so we set it to directory
$isDirectory = true;
}
} else {
$type = str('volume');
}
} elseif (is_array($volume)) {
$type = data_get_str($volume, 'type');
$source = data_get_str($volume, 'source');
$target = data_get_str($volume, 'target');
$content = data_get($volume, 'content');
$isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null);
$foundConfig = $fileStorages->whereMountPath($target)->first();
if ($foundConfig) {
$contentNotNull_temp = data_get($foundConfig, 'content');
if ($contentNotNull_temp) {
$content = $contentNotNull_temp;
}
$isDirectory = data_get($foundConfig, 'is_directory');
} else {
// if isDirectory is not set (or false) & content is also not set, we assume it is a directory
if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) {
$isDirectory = true;
}
}
}
if ($type->value() === 'bind') {
if ($source->value() === '/var/run/docker.sock') {
return $volume;
}
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
return $volume;
}
$mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
$source = replaceLocalSource($source, $mainDirectory);
if ($isPullRequest) {
$source = $source."-pr-$pullRequestId";
}
if (
! $this?->settings?->is_preserve_repository_enabled || $foundConfig?->is_based_on_git
) {
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $this->id,
'resource_type' => get_class($this),
],
[
'fs_path' => $source,
'mount_path' => $target,
'content' => $content,
'is_directory' => $isDirectory,
'resource_id' => $this->id,
'resource_type' => get_class($this),
]
);
}
$volume = "$source:$target";
} elseif ($type->value() === 'volume') {
if ($topLevel->get('volumes')->has($source->value())) {
$temp = $topLevel->get('volumes')->get($source->value());
if (data_get($temp, 'driver_opts.type') === 'cifs') {
return $volume;
}
if (data_get($temp, 'driver_opts.type') === 'nfs') {
return $volume;
}
}
$slugWithoutUuid = Str::slug($source, '-');
$name = "{$uuid}_{$slugWithoutUuid}";
if ($isPullRequest) {
$name = "{$name}-pr-$pullRequestId";
}
if (is_string($volume)) {
$source = str($volume)->before(':');
$target = str($volume)->after(':')->beforeLast(':');
$source = $name;
$volume = "$source:$target";
} elseif (is_array($volume)) {
data_set($volume, 'source', $name);
}
$topLevel->get('volumes')->put($name, [
'name' => $name,
]);
LocalPersistentVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $this->id,
'resource_type' => get_class($this),
],
[
'name' => $name,
'mount_path' => $target,
'resource_id' => $this->id,
'resource_type' => get_class($this),
]
);
}
dispatch(new ServerFilesFromServerJob($this));
$volumesParsed->put($index, $volume);
}
}
if ($depends_on?->count() > 0) {
if ($isPullRequest) {
$newDependsOn = collect([]);
$depends_on->each(function ($dependency, $condition) use ($pullRequestId, $newDependsOn) {
if (is_numeric($condition)) {
$dependency = "$dependency-pr-$pullRequestId";
$newDependsOn->put($condition, $dependency);
} else {
$condition = "$condition-pr-$pullRequestId";
$newDependsOn->put($condition, $dependency);
}
});
$depends_on = $newDependsOn;
}
}
if ($topLevel->get('networks')?->count() > 0) {
foreach ($topLevel->get('networks') as $networkName => $network) {
if ($networkName === 'default') {
continue;
}
// ignore aliases
if ($network['aliases'] ?? false) {
continue;
}
$networkExists = $networks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
$networks->put($networkName, null);
}
}
}
$baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) {
return $value == $baseNetwork;
});
if (! $baseNetworkExists) {
foreach ($baseNetwork as $network) {
$topLevel->get('networks')->put($network, [
'name' => $network,
'external' => true,
]);
}
}
$networks_temp = collect();
foreach ($networks as $key => $network) {
if (gettype($network) === 'string') {
// networks:
// - appwrite
$networks_temp->put($network, null);
} elseif (gettype($network) === 'array') {
// networks:
// default:
// ipv4_address: 192.168.203.254
$networks_temp->put($key, $network);
}
}
foreach ($baseNetwork as $key => $network) {
$networks_temp->put($network, null);
}
if (data_get($this, 'settings.connect_to_docker_network')) {
$network = $this->destination->network;
$networks_temp->put($network, null);
$topLevel->get('networks')->put($network, [
'name' => $network,
'external' => true,
]);
}
foreach ($environment as $key => $value) {
if (is_numeric($key)) {
if (is_array($value)) {
// - SESSION_SECRET: 123
// - SESSION_SECRET:
$key = str(collect($value)->keys()->first());
$value = str(collect($value)->values()->first());
} else {
$variable = str($value);
if ($variable->contains('=')) {
// - SESSION_SECRET=123
// - SESSION_SECRET=
$key = $variable->before('=');
$value = $variable->after('=');
} else {
// - SESSION_SECRET
$key = $variable;
$value = null;
}
}
} else {
// SESSION_SECRET: 123
// SESSION_SECRET:
$key = str($key);
$value = str($value);
}
// Auto generate FQDN and URL
if ($key->startsWith('SERVICE_FQDN') || $value->startsWith('$SERVICE_FQDN') || $value->startsWith('${SERVICE_FQDN')) {
if ($value->contains('SERVICE_FQDN')) {
$key = str(replaceVariables($value));
$value = null;
}
$name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower();
$fqdn = generateFqdn($server, "{$name->value()}-{$uuid}");
$url = str($fqdn)->replace('http://', '')->replace('https://', '')->replace('www.', '');
$keyUrl = $key->replace('SERVICE_FQDN', 'SERVICE_URL');
// TODO: is this needed?
// if (substr_count($key->value(), '_') === 3) {
// // SERVICE_FQDN_UMAMI_1000
// $port = $key->afterLast('_');
// } else {
// // SERVICE_FQDN_UMAMI
// $port = null;
// }
// if ($port) {
// $fqdn = "$fqdn:$port";
// }
if ($value && get_class($value) === 'Illuminate\Support\Stringable') {
$path = $value->value();
$fqdn = "$fqdn$path";
}
// ray([
// 'key' => $key,
// 'value' => $fqdn,
// ]);
// ray($this->environment_variables()->where('key', $key)->where('application_id', $this->id)->first());
$this->environment_variables()->where('key', $key)->where('application_id', $this->id)->firstOrCreate([
'key' => $key,
'application_id' => $this->id,
], [
'value' => $fqdn,
'is_build_time' => false,
'is_preview' => false,
]);
$this->environment_variables()->where('key', $keyUrl)->where('application_id', $this->id)->firstOrCreate([
'key' => $keyUrl,
'application_id' => $this->id,
], [
'value' => $url,
'is_build_time' => false,
'is_preview' => false,
]);
} elseif ($value->startsWith('$')) {
// If the value is a variable then we will add it to Coolify's DB
// ${VARIABLE} will be VARIABLE instead
$value = str(replaceVariables($value));
if ($value->contains(':-')) {
$key = $value->before(':');
$defaultValue = $value->after(':-');
} elseif ($value->contains('-')) {
$key = $value->before('-');
$defaultValue = $value->after('-');
} elseif ($value->contains(':?')) {
$key = $value->before(':');
$defaultValue = $value->after(':?');
} elseif ($value->contains('?')) {
$key = $value->before('?');
$defaultValue = $value->after('?');
} else {
$key = $value;
$defaultValue = null;
}
$this->environment_variables()->where('key', $key)->where('application_id', $this->id)->firstOrCreate([
'key' => $key,
'application_id' => $this->id,
'is_preview' => false,
], [
'value' => $defaultValue,
'is_build_time' => false,
'is_preview' => false,
]);
}
}
$branch = $this->git_branch;
if ($pullRequestId !== 0) {
$branch = "pull/{$pullRequestId}/head";
}
if ($this->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$environment->put('COOLIFY_BRANCH', $branch);
}
if ($this->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
$environment->put('COOLIFY_CONTAINER_NAME', $containerName);
}
add_coolify_default_environment_variables($this->database, $environment, $this->environment_variables);
// Remove SERVICE_FQDN and SERVICE_URL from environment
$environment = $environment->filter(function ($value, $key) {
return ! str($key)->startsWith('SERVICE_FQDN') && ! str($key)->startsWith('SERVICE_URL');
});
// Labels
$fqdns = collect([]);
$domains = collect(json_decode($this->docker_compose_domains)) ?? collect([]);
if ($domains->count() !== 0) {
$fqdns = data_get($domains, "$serviceName.domain");
if (! $fqdns) {
$fqdns = collect([]);
} else {
$fqdns = str($fqdns)->explode(',');
if ($isPullRequest) {
$preview = $this->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");
if ($found_fqdn) {
$fqdns = collect($found_fqdn);
} else {
$fqdns = collect([]);
}
} else {
$fqdns = $fqdns->map(function ($fqdn) use ($pullRequestId) {
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->id, $pullRequestId);
$url = Url::fromString($fqdn);
$template = $this->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2;
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$preview->fqdn = $preview_fqdn;
$preview->save();
return $preview_fqdn;
});
}
}
$shouldGenerateLabelsExactly = $server->settings->generate_exact_labels;
if ($shouldGenerateLabelsExactly) {
switch ($server->proxyType()) {
case ProxyTypes::TRAEFIK->value:
$labels = $labels->merge(
fqdnLabelsForTraefik(
uuid: $this->uuid,
domains: $fqdns,
serviceLabels: $labels,
generate_unique_uuid: $this->build_pack === 'dockercompose',
image: $image,
is_force_https_enabled: $this->isForceHttpsEnabled(),
is_gzip_enabled: $this->isGzipEnabled(),
is_stripprefix_enabled: $this->isStripprefixEnabled(),
)
);
break;
case ProxyTypes::CADDY->value:
$labels = $labels->merge(
fqdnLabelsForCaddy(
network: $this->destination->network,
uuid: $this->uuid,
domains: $fqdns,
serviceLabels: $labels,
image: $image,
is_force_https_enabled: $this->isForceHttpsEnabled(),
is_gzip_enabled: $this->isGzipEnabled(),
is_stripprefix_enabled: $this->isStripprefixEnabled(),
)
);
break;
}
} else {
$labels = $labels->merge(
fqdnLabelsForTraefik(
uuid: $this->uuid,
domains: $fqdns,
serviceLabels: $labels,
generate_unique_uuid: $this->build_pack === 'dockercompose',
image: $image,
is_force_https_enabled: $this->isForceHttpsEnabled(),
is_gzip_enabled: $this->isGzipEnabled(),
is_stripprefix_enabled: $this->isStripprefixEnabled(),
)
);
$labels = $labels->merge(
fqdnLabelsForCaddy(
network: $this->destination->network,
uuid: $this->uuid,
domains: $fqdns,
serviceLabels: $labels,
image: $image,
is_force_https_enabled: $this->isForceHttpsEnabled(),
is_gzip_enabled: $this->isGzipEnabled(),
is_stripprefix_enabled: $this->isStripprefixEnabled(),
)
);
}
}
}
$defaultLabels = defaultLabels(
id: $this->id,
name: $containerName,
pull_request_id: $pullRequestId,
type: 'application');
$labels = $labels->merge($defaultLabels);
if ($labels->count() > 0 && $this->settings->is_container_label_escape_enabled) {
$labels = $labels->map(function ($value, $key) {
return escapeDollarSign($value);
});
}
$payload = collect($service)->merge([
'restart' => $restart->value(),
'container_name' => $containerName,
'volumes' => $volumesParsed,
'networks' => $networks_temp,
'labels' => $labels,
'environment' => $environment,
]);
if ($logging) {
$payload['logging'] = $logging;
}
if ($depends_on->count() > 0) {
$payload['depends_on'] = $depends_on;
}
if ($isPullRequest) {
$serviceName = "{$serviceName}-pr-{$pullRequestId}";
}
$parsedServices->put($serviceName, $payload);
}
$topLevel->put('services', $parsedServices);
$customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets'];
$topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) {
return array_search($key, $customOrder);
});
$this->docker_compose = Yaml::dump(convertToArray($topLevel), 10, 2);
data_forget($this, 'environment_variables');
data_forget($this, 'environment_variables_preview');
$this->save();
return $topLevel;
}
public function oldParser(int $pull_request_id = 0, ?int $preview_id = null)
public function parse(int $pull_request_id = 0, ?int $preview_id = null)
{
if ($this->compose_parsing_version === '3') {
return $this->newParser($pull_request_id, $preview_id);
return newParser($this, $pull_request_id, $preview_id);
} elseif ($this->docker_compose_raw) {
return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id);
} else {
@@ -1675,7 +1142,7 @@ class Application extends BaseModel
if ($composeFileContent) {
$this->docker_compose_raw = $composeFileContent;
$this->save();
$parsedServices = $this->oldParser();
$parsedServices = $this->parse();
if ($this->docker_compose_domains) {
$json = collect(json_decode($this->docker_compose_domains));
$names = collect(data_get($parsedServices, 'services'))->keys()->toArray();

View File

@@ -12,9 +12,9 @@ class ApplicationPreview extends BaseModel
protected static function booted()
{
static::deleting(function ($preview) {
if ($preview->application->build_pack === 'dockercompose') {
if (data_get($preview, 'application.build_pack') === 'dockercompose') {
$server = $preview->application->destination->server;
$composeFile = $preview->application->oldParser(pull_request_id: $preview->pull_request_id);
$composeFile = $preview->application->parse(pull_request_id: $preview->pull_request_id);
$volumes = data_get($composeFile, 'volumes');
$networks = data_get($composeFile, 'networks');
$networkKeys = collect($networks)->keys();

View File

@@ -2,14 +2,11 @@
namespace App\Models;
use App\Enums\ProxyTypes;
use App\Jobs\ServerFilesFromServerJob;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use OpenApi\Attributes as OA;
use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml;
@@ -42,6 +39,8 @@ class Service extends BaseModel
{
use HasFactory, SoftDeletes;
private static $parserVersion = '3';
protected $guarded = [];
protected $appends = ['server_status'];
@@ -49,7 +48,7 @@ class Service extends BaseModel
protected static function booted()
{
static::created(function ($service) {
$service->compose_parsing_version = '2';
$service->compose_parsing_version = self::$parserVersion;
$service->save();
});
}
@@ -676,6 +675,32 @@ class Service extends BaseModel
$fields->put('GitLab', $data->toArray());
break;
case str($image)->contains('code-server'):
$data = collect([]);
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_64_PASSWORDCODESERVER')->first();
if ($password) {
$data = $data->merge([
'Password' => [
'key' => data_get($password, 'key'),
'value' => data_get($password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
$sudoPassword = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_SUDOCODESERVER')->first();
if ($sudoPassword) {
$data = $data->merge([
'Sudo Password' => [
'key' => data_get($sudoPassword, 'key'),
'value' => data_get($sudoPassword, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
$fields->put('Code Server', $data->toArray());
break;
}
}
$databases = $this->databases()->get();
@@ -722,8 +747,8 @@ class Service extends BaseModel
$fields->put('PostgreSQL', $data->toArray());
break;
case str($image)->contains('mysql'):
$userVariables = ['SERVICE_USER_MYSQL', 'SERVICE_USER_WORDPRESS'];
$passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS'];
$userVariables = ['SERVICE_USER_MYSQL', 'SERVICE_USER_WORDPRESS', 'MYSQL_USER'];
$passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS', 'MYSQL_PASSWORD'];
$rootPasswordVariables = ['SERVICE_PASSWORD_MYSQLROOT', 'SERVICE_PASSWORD_ROOT'];
$dbNameVariables = ['MYSQL_DATABASE'];
$mysql_user = $this->environment_variables()->whereIn('key', $userVariables)->first();
@@ -772,10 +797,10 @@ class Service extends BaseModel
$fields->put('MySQL', $data->toArray());
break;
case str($image)->contains('mariadb'):
$userVariables = ['SERVICE_USER_MARIADB', 'SERVICE_USER_WORDPRESS', '_APP_DB_USER'];
$passwordVariables = ['SERVICE_PASSWORD_MARIADB', 'SERVICE_PASSWORD_WORDPRESS', '_APP_DB_PASS'];
$rootPasswordVariables = ['SERVICE_PASSWORD_MARIADBROOT', 'SERVICE_PASSWORD_ROOT', '_APP_DB_ROOT_PASS'];
$dbNameVariables = ['SERVICE_DATABASE_MARIADB', 'SERVICE_DATABASE_WORDPRESS', '_APP_DB_SCHEMA'];
$userVariables = ['SERVICE_USER_MARIADB', 'SERVICE_USER_WORDPRESS', '_APP_DB_USER', 'SERVICE_USER_MYSQL', 'MYSQL_USER'];
$passwordVariables = ['SERVICE_PASSWORD_MARIADB', 'SERVICE_PASSWORD_WORDPRESS', '_APP_DB_PASS', 'MYSQL_PASSWORD'];
$rootPasswordVariables = ['SERVICE_PASSWORD_MARIADBROOT', 'SERVICE_PASSWORD_ROOT', '_APP_DB_ROOT_PASS', 'MYSQL_ROOT_PASSWORD'];
$dbNameVariables = ['SERVICE_DATABASE_MARIADB', 'SERVICE_DATABASE_WORDPRESS', '_APP_DB_SCHEMA', 'MYSQL_DATABASE'];
$mariadb_user = $this->environment_variables()->whereIn('key', $userVariables)->first();
$mariadb_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first();
$mariadb_root_password = $this->environment_variables()->whereIn('key', $rootPasswordVariables)->first();
@@ -822,6 +847,7 @@ class Service extends BaseModel
}
$fields->put('MariaDB', $data->toArray());
break;
}
}
@@ -956,7 +982,8 @@ class Service extends BaseModel
public function environment_variables(): HasMany
{
return $this->hasMany(EnvironmentVariable::class)->orderBy('key', 'asc');
return $this->hasMany(EnvironmentVariable::class)->orderByRaw("key LIKE 'SERVICE%' DESC, value ASC");
}
public function environment_variables_preview(): HasMany
@@ -992,531 +1019,10 @@ class Service extends BaseModel
instant_remote_process($commands, $this->server);
}
public function newParser()
{
return newParser($this);
$uuid = data_get($this, 'uuid');
$server = data_get($this, 'destination.server');
$compose = data_get($this, 'docker_compose_raw');
try {
$yaml = Yaml::parse($compose);
} catch (\Exception $e) {
return;
}
$allServices = get_service_templates();
$services = data_get($yaml, 'services', collect([]));
$topLevel = collect([
'volumes' => collect(data_get($yaml, 'volumes', [])),
'networks' => collect(data_get($yaml, 'networks', [])),
'configs' => collect(data_get($yaml, 'configs', [])),
'secrets' => collect(data_get($yaml, 'secrets', [])),
]);
// If there are predefined volumes, make sure they are not null
if ($topLevel->get('volumes')->count() > 0) {
$temp = collect([]);
foreach ($topLevel['volumes'] as $volumeName => $volume) {
if (is_null($volume)) {
continue;
}
$temp->put($volumeName, $volume);
}
$topLevel['volumes'] = $temp;
}
// Get the base docker network
$baseNetwork = collect([$uuid]);
$parsedServices = collect([]);
// Let's loop through the services
foreach ($services as $serviceName => $service) {
if ($serviceName === 'registry') {
$tempServiceName = 'docker-registry';
} else {
$tempServiceName = $serviceName;
}
if (str(data_get($service, 'image'))->contains('glitchtip')) {
$tempServiceName = 'glitchtip';
}
if ($serviceName === 'supabase-kong') {
$tempServiceName = 'supabase';
}
$serviceDefinition = data_get($allServices, $tempServiceName);
$predefinedPort = data_get($serviceDefinition, 'port');
if ($serviceName === 'plausible') {
$predefinedPort = '8000';
}
$image = data_get_str($service, 'image');
$restart = data_get_str($service, 'restart', RESTART_MODE);
$logging = data_get($service, 'logging');
if ($server->isLogDrainEnabled() && $this->isLogDrainEnabled()) {
$logging = generate_fluentd_configuration();
}
$volumes = collect(data_get($service, 'volumes', []));
$networks = collect(data_get($service, 'networks', []));
$labels = collect(data_get($service, 'labels', []));
$environment = collect(data_get($service, 'environment', []));
$buildArgs = collect(data_get($service, 'build.args', []));
$environment = $environment->merge($buildArgs);
$hasHostNetworkMode = data_get($service, 'network_mode') === 'host' ? true : false;
$containerName = "$serviceName-{$this->uuid}";
$isDatabase = isDatabaseImage(data_get_str($service, 'image'));
$volumesParsed = collect([]);
if ($isDatabase) {
$savedService = ServiceDatabase::firstOrCreate([
'name' => $serviceName,
'image' => $image,
'service_id' => $this->id,
]);
} else {
$savedService = ServiceApplication::firstOrCreate([
'name' => $serviceName,
'image' => $image,
'service_id' => $this->id,
]);
}
$fileStorages = $savedService->fileStorages();
// Check if image changed
if ($savedService->image !== $image) {
$savedService->image = $image;
$savedService->save();
}
if ($volumes->count() > 0) {
foreach ($volumes as $index => $volume) {
$type = null;
$source = null;
$target = null;
$content = null;
$isDirectory = false;
if (is_string($volume)) {
$source = str($volume)->before(':');
$target = str($volume)->after(':')->beforeLast(':');
$foundConfig = $fileStorages->whereMountPath($target)->first();
if (sourceIsLocal($source)) {
$type = str('bind');
if ($foundConfig) {
$contentNotNull_temp = data_get($foundConfig, 'content');
if ($contentNotNull_temp) {
$content = $contentNotNull_temp;
}
$isDirectory = data_get($foundConfig, 'is_directory');
} else {
// By default, we cannot determine if the bind is a directory or not, so we set it to directory
$isDirectory = true;
}
} else {
$type = str('volume');
}
} elseif (is_array($volume)) {
$type = data_get_str($volume, 'type');
$source = data_get_str($volume, 'source');
$target = data_get_str($volume, 'target');
$content = data_get($volume, 'content');
$isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null);
$foundConfig = $fileStorages->whereMountPath($target)->first();
if ($foundConfig) {
$contentNotNull_temp = data_get($foundConfig, 'content');
if ($contentNotNull_temp) {
$content = $contentNotNull_temp;
}
$isDirectory = data_get($foundConfig, 'is_directory');
} else {
// if isDirectory is not set (or false) & content is also not set, we assume it is a directory
if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) {
$isDirectory = true;
}
}
}
if ($type->value() === 'bind') {
if ($source->value() === '/var/run/docker.sock') {
return $volume;
}
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
return $volume;
}
$mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
$source = replaceLocalSource($source, $mainDirectory);
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService),
],
[
'fs_path' => $source,
'mount_path' => $target,
'content' => $content,
'is_directory' => $isDirectory,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService),
]
);
$volume = "$source:$target";
} elseif ($type->value() === 'volume') {
if ($topLevel->get('volumes')->has($source->value())) {
$temp = $topLevel->get('volumes')->get($source->value());
if (data_get($temp, 'driver_opts.type') === 'cifs') {
return $volume;
}
if (data_get($temp, 'driver_opts.type') === 'nfs') {
return $volume;
}
}
$slugWithoutUuid = Str::slug($source, '-');
$name = "{$uuid}_{$slugWithoutUuid}";
if (is_string($volume)) {
$source = str($volume)->before(':');
$target = str($volume)->after(':')->beforeLast(':');
$source = $name;
$volume = "$source:$target";
} elseif (is_array($volume)) {
data_set($volume, 'source', $name);
}
$topLevel->get('volumes')->put($name, [
'name' => $name,
]);
LocalPersistentVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService),
],
[
'name' => $name,
'mount_path' => $target,
'resource_id' => $savedService->id,
'resource_type' => get_class($savedService),
]
);
}
dispatch(new ServerFilesFromServerJob($savedService));
$volumesParsed->put($index, $volume);
}
}
if ($topLevel->get('networks')?->count() > 0) {
foreach ($topLevel->get('networks') as $networkName => $network) {
if ($networkName === 'default') {
continue;
}
// ignore aliases
if ($network['aliases'] ?? false) {
continue;
}
$networkExists = $networks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
$networks->put($networkName, null);
}
}
}
$baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) {
return $value == $baseNetwork;
});
if (! $baseNetworkExists) {
foreach ($baseNetwork as $network) {
$topLevel->get('networks')->put($network, [
'name' => $network,
'external' => true,
]);
}
}
$networks_temp = collect();
foreach ($networks as $key => $network) {
if (gettype($network) === 'string') {
// networks:
// - appwrite
$networks_temp->put($network, null);
} elseif (gettype($network) === 'array') {
// networks:
// default:
// ipv4_address: 192.168.203.254
$networks_temp->put($key, $network);
}
}
foreach ($baseNetwork as $key => $network) {
$networks_temp->put($network, null);
}
// Convert
// - SESSION_SECRET: 123 to - SESSION_SECRET=123
$convertedServiceVariables = collect([]);
foreach ($environment as $variableName => $variable) {
if (is_numeric($variableName)) {
if (is_array($variable)) {
$key = str(collect($variable)->keys()->first());
$value = str(collect($variable)->values()->first());
$variable = "$key=$value";
$convertedServiceVariables->put($variableName, $variable);
} elseif (is_string($variable)) {
$convertedServiceVariables->put($variableName, $variable);
}
} elseif (is_string($variableName)) {
$convertedServiceVariables->put($variableName, $variable);
}
}
$environment = $convertedServiceVariables;
// filter magic environments
$magicEnvironments = $environment->filter(function ($value, $key) {
return str($key)->startsWith('SERVICE_FQDN') || str($key)->startsWith('SERVICE_URL') || str($value)->startsWith('SERVICE_FQDN') || str($value)->startsWith('SERVICE_URL');
});
if ($magicEnvironments->count() > 0) {
foreach ($magicEnvironments as $key => $value) {
$key = str($key);
$value = str($value);
$command = $key->after('SERVICE_')->beforeLast('_');
if ($command->value() === 'FQDN') {
$fqdn = generateFqdn($server, "{$savedService->name}-{$uuid}");
if ($value && get_class($value) === 'Illuminate\Support\Stringable' && $value->startsWith('/')) {
$path = $value->value();
$value = "$fqdn$path";
} else {
$value = $fqdn;
}
} elseif ($command->value() === 'URL') {
$fqdn = generateFqdn($server, "{$savedService->name}-{$uuid}");
$value = str($fqdn)->replace('http://', '')->replace('https://', '')->replace('www.', '');
}
if (! $isDatabase && ! $this->environment_variables()->where('key', $key)->where('service_id', $this->id)->first()) {
$savedService->fqdn = $value;
$savedService->save();
}
$this->environment_variables()->where('key', $key)->where('service_id', $this->id)->firstOrCreate([
'key' => $key,
'service_id' => $this->id,
], [
'value' => $value,
'is_build_time' => false,
'is_preview' => false,
]);
}
}
foreach ($environment as $key => $value) {
if (is_numeric($key)) {
if (is_array($value)) {
// - SESSION_SECRET: 123
// - SESSION_SECRET:
$key = str(collect($value)->keys()->first());
$value = str(collect($value)->values()->first());
} else {
$variable = str($value);
if ($variable->contains('=')) {
// - SESSION_SECRET=123
// - SESSION_SECRET=
$key = $variable->before('=');
$value = $variable->after('=');
} else {
// - SESSION_SECRET
$key = $variable;
$value = null;
}
}
} else {
// SESSION_SECRET: 123
// SESSION_SECRET:
$key = str($key);
$value = str($value);
}
// Auto generate FQDN and URL
// environment:
// - SERVICE_FQDN_UMAMI=/umami
// - FQDN=$SERVICE_FQDN_UMAMI
// - URL=$SERVICE_URL_UMAMI
// - TEST=${TEST:-initial}
// - HARDCODED=stuff
if ($value->startsWith('$')) {
$value = str(replaceVariables($value));
if ($value->startsWith('SERVICE_')) {
// $value = SERVICE_FQDN_UMAMI
$command = $value->after('SERVICE_')->beforeLast('_');
if ($command->value() === 'FQDN') {
if ($magicEnvironments->has($value->value())) {
$found = $magicEnvironments->get($value->value());
if ($found) {
$found = $this->environment_variables()->where('key', $value->value())->where('service_id', $this->id)->first();
if ($found) {
$value = $found->value;
}
}
} else {
$fqdn = generateFqdn($server, "{$savedService->name}-{$uuid}");
if ($value && get_class($value) === 'Illuminate\Support\Stringable' && $value->startsWith('/')) {
$path = $value->value();
$value = "$fqdn$path";
} else {
$value = $fqdn;
}
}
} elseif ($command->value() === 'URL') {
if ($magicEnvironments->has($value->value())) {
$found = $magicEnvironments->get($value->value());
if ($found) {
$found = $this->environment_variables()->where('key', $value->value())->where('service_id', $this->id)->first();
if ($found) {
$value = str($found->value)->replace('http://', '')->replace('https://', '')->replace('www.', '');
}
}
} else {
$fqdn = generateFqdn($server, "{$savedService->name}-{$uuid}");
$value = str($fqdn)->replace('http://', '')->replace('https://', '')->replace('www.', '');
}
} else {
$value = generateEnvValue($command, $this);
}
$this->environment_variables()->where('key', $key)->where('service_id', $this->id)->firstOrCreate([
'key' => $key,
'service_id' => $this->id,
], [
'value' => $value,
'is_build_time' => false,
'is_preview' => false,
]);
} else {
if ($value->contains(':-')) {
$key = $value->before(':');
$value = $value->after(':-');
} elseif ($value->contains('-')) {
$key = $value->before('-');
$value = $value->after('-');
} elseif ($value->contains(':?')) {
$key = $value->before(':');
$value = $value->after(':?');
} elseif ($value->contains('?')) {
$key = $value->before('?');
$value = $value->after('?');
} else {
$key = $value;
$value = null;
}
$this->environment_variables()->where('key', $key)->where('service_id', $this->id)->firstOrCreate([
'key' => $key,
'service_id' => $this->id,
], [
'value' => $value,
'is_build_time' => false,
'is_preview' => false,
]);
}
}
if ($this->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
$environment->put('COOLIFY_CONTAINER_NAME', $containerName);
}
// Remove SERVICE_FQDN and SERVICE_URL from environment
$environment = $environment->filter(function ($value, $key) {
return ! str($key)->startsWith('SERVICE_FQDN') && ! str($key)->startsWith('SERVICE_URL');
});
}
if ($savedService->serviceType()) {
$fqdns = generateServiceSpecificFqdns($savedService);
} else {
$fqdns = collect(data_get($savedService, 'fqdns'))->filter();
}
$defaultLabels = defaultLabels($this->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id);
$serviceLabels = $labels->merge($defaultLabels);
if (! $isDatabase && $fqdns->count() > 0) {
if ($fqdns) {
$shouldGenerateLabelsExactly = $this->server->settings->generate_exact_labels;
if ($shouldGenerateLabelsExactly) {
switch ($this->server->proxyType()) {
case ProxyTypes::TRAEFIK->value:
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
uuid: $this->uuid,
domains: $fqdns,
is_force_https_enabled: true,
serviceLabels: $serviceLabels,
is_gzip_enabled: $savedService->isGzipEnabled(),
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
service_name: $serviceName,
image: data_get($service, 'image')
));
break;
case ProxyTypes::CADDY->value:
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
network: $this->destination->network,
uuid: $this->uuid,
domains: $fqdns,
is_force_https_enabled: true,
serviceLabels: $serviceLabels,
is_gzip_enabled: $savedService->isGzipEnabled(),
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
service_name: $serviceName,
image: data_get($service, 'image')
));
break;
}
} else {
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik(
uuid: $this->uuid,
domains: $fqdns,
is_force_https_enabled: true,
serviceLabels: $serviceLabels,
is_gzip_enabled: $savedService->isGzipEnabled(),
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
service_name: $serviceName,
image: data_get($service, 'image')
));
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
network: $this->destination->network,
uuid: $this->uuid,
domains: $fqdns,
is_force_https_enabled: true,
serviceLabels: $serviceLabels,
is_gzip_enabled: $savedService->isGzipEnabled(),
is_stripprefix_enabled: $savedService->isStripprefixEnabled(),
service_name: $serviceName,
image: data_get($service, 'image')
));
}
}
}
$payload = collect($service)->merge([
'restart' => $restart->value(),
'container_name' => $containerName,
'volumes' => $volumesParsed,
'networks' => $networks_temp,
'labels' => $serviceLabels,
'environment' => $environment,
]);
if ($logging) {
$payload['logging'] = $logging;
}
$parsedServices->put($serviceName, $payload);
}
$topLevel->put('services', $parsedServices);
$customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets'];
$topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) {
return array_search($key, $customOrder);
});
$this->docker_compose = Yaml::dump(convertToArray($topLevel), 10, 2);
data_forget($this, 'environment_variables');
data_forget($this, 'environment_variables_preview');
$this->save();
return $topLevel;
}
public function parse(bool $isNew = false): Collection
{
if ($this->compose_parsing_version === '3') {
return $this->newParser();
return newParser($this);
} elseif ($this->docker_compose_raw) {
return parseDockerComposeFile($this, $isNew);
} else {