feat: preserve git repository with advanced file storages

This commit is contained in:
Andras Bacsai 2024-08-12 16:06:24 +02:00
parent f87e6bcfc6
commit 0e54ed1343
7 changed files with 542 additions and 297 deletions

View File

@ -157,7 +157,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private ?string $coolify_variables = null;
private bool $preserveRepository = true;
private bool $preserveRepository = false;
public $tries = 1;
@ -480,6 +480,7 @@ private function deploy_docker_compose_buildpack()
}
// Start compose file
$server_workdir = $this->application->workdir();
if ($this->application->settings->is_raw_compose_deployment_enabled) {
if ($this->docker_compose_custom_start_command) {
$this->write_deployment_configurations();
@ -488,7 +489,6 @@ private function deploy_docker_compose_buildpack()
);
} else {
$this->write_deployment_configurations();
$server_workdir = $this->application->workdir();
$this->docker_compose_location = '/docker-compose.yaml';
$command = "{$this->coolify_variables} docker compose";
@ -508,15 +508,26 @@ private function deploy_docker_compose_buildpack()
);
} else {
$command = "{$this->coolify_variables} docker compose";
if ($this->env_filename) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
}
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
if ($this->preserveRepository) {
if ($this->env_filename) {
$command .= " --env-file {$server_workdir}/{$this->env_filename}";
}
$command .= " --project-name {$this->application->uuid} --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d";
$this->write_deployment_configurations();
$this->execute_remote_command(
['command' => $command, 'hidden' => true],
);
} else {
if ($this->env_filename) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
}
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
);
}
$this->write_deployment_configurations();
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
);
}
}
@ -619,6 +630,11 @@ private function write_deployment_configurations()
],
);
}
$this->application->fileStorages()->each(function ($fileStorage) {
if (! $fileStorage->is_based_on_git && ! $fileStorage->is_directory) {
$fileStorage->saveStorageOnServer();
}
});
if ($this->use_build_server) {
$this->server = $this->build_server;
}

View File

@ -3,7 +3,6 @@
namespace App\Livewire\Project\Application;
use App\Models\Application;
use App\Models\LocalFileVolume;
use Illuminate\Support\Collection;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@ -30,6 +29,8 @@ class General extends Component
public ?string $ports_exposes = null;
public bool $is_preserve_repository_enabled = false;
public bool $is_container_label_escape_enabled = true;
public $customLabels;
@ -145,6 +146,7 @@ public function mount()
}
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
$this->ports_exposes = $this->application->ports_exposes;
$this->is_preserve_repository_enabled = $this->application->settings->is_preserve_repository_enabled;
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
$this->customLabels = $this->application->parseContainerLabels();
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) {
@ -168,9 +170,21 @@ public function instantSave()
$this->application->settings->save();
$this->dispatch('success', 'Settings saved.');
$this->application->refresh();
// If port_exposes changed, reset default labels
if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) {
$this->resetDefaultLabels(false);
}
if ($this->is_preserve_repository_enabled !== $this->application->settings->is_preserve_repository_enabled) {
if ($this->application->settings->is_preserve_repository_enabled === false) {
$this->application->fileStorages->each(function ($storage) {
$storage->is_based_on_git = $this->application->settings->is_preserve_repository_enabled;
$storage->save();
});
}
}
}
public function loadComposeFile($isInit = false)
@ -191,32 +205,6 @@ public function loadComposeFile($isInit = false)
return;
}
$compose = $this->application->parseCompose();
$services = data_get($compose, 'services');
if ($services) {
$volumes = collect($services)->map(function ($service) {
return data_get($service, 'volumes');
})->flatten()->filter(function ($volume) {
return str($volume)->startsWith('/data/coolify');
})->unique()->values();
foreach ($volumes as $volume) {
$source = str($volume)->before(':');
$target = str($volume)->after(':')->beforeLast(':');
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $this->application->id,
'resource_type' => get_class($this->application),
],
[
'fs_path' => $source,
'mount_path' => $target,
'resource_id' => $this->application->id,
'resource_type' => get_class($this->application),
]
);
}
}
$this->dispatch('success', 'Docker compose file loaded.');
$this->dispatch('compose_loaded');
$this->dispatch('refreshStorages');

View File

@ -33,6 +33,7 @@ class FileStorage extends Component
'fileStorage.fs_path' => 'required',
'fileStorage.mount_path' => 'required',
'fileStorage.content' => 'nullable',
'fileStorage.is_based_on_git' => 'required|boolean',
];
public function mount()
@ -45,6 +46,7 @@ public function mount()
$this->workdir = null;
$this->fs_path = $this->fileStorage->fs_path;
}
$this->fileStorage->loadStorageOnServer();
}
public function convertToDirectory()
@ -68,6 +70,9 @@ public function convertToFile()
$this->fileStorage->deleteStorageOnServer();
$this->fileStorage->is_directory = false;
$this->fileStorage->content = null;
if (data_get($this->resource, 'settings.is_preserve_repository_enabled')) {
$this->fileStorage->is_based_on_git = true;
}
$this->fileStorage->save();
$this->fileStorage->saveStorageOnServer();
} catch (\Throwable $e) {

View File

@ -24,6 +24,32 @@ public function service()
return $this->morphTo('resource');
}
public function loadStorageOnServer()
{
$this->load(['service']);
$isService = data_get($this->resource, 'service');
if ($isService) {
$workdir = $this->resource->service->workdir();
$server = $this->resource->service->server;
} else {
$workdir = $this->resource->workdir();
$server = $this->resource->destination->server;
}
$commands = collect([]);
$path = data_get_str($this, 'fs_path');
if ($path->startsWith('.')) {
$path = $path->after('.');
$path = $workdir.$path;
}
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
if ($isFile === 'OK') {
$content = instant_remote_process(["cat $path"], $server, false);
$this->content = $content;
$this->is_directory = false;
$this->save();
}
}
public function deleteStorageOnServer()
{
$isService = data_get($this->resource, 'service');
@ -35,17 +61,20 @@ public function deleteStorageOnServer()
$server = $this->resource->destination->server;
}
$commands = collect([]);
$fs_path = data_get($this, 'fs_path');
$isFile = instant_remote_process(["test -f $fs_path && echo OK || echo NOK"], $server);
$isDir = instant_remote_process(["test -d $fs_path && echo OK || echo NOK"], $server);
if ($fs_path && $fs_path != '/' && $fs_path != '.' && $fs_path != '..') {
ray($isFile, $isDir);
$path = data_get_str($this, 'fs_path');
if ($path->startsWith('.')) {
$path = $path->after('.');
$path = $workdir.$path;
}
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
if ($path && $path != '/' && $path != '.' && $path != '..') {
if ($isFile === 'OK') {
$commands->push("rm -rf $fs_path > /dev/null 2>&1 || true");
$commands->push("rm -rf $path > /dev/null 2>&1 || true");
} elseif ($isDir === 'OK') {
$commands->push("rm -rf $fs_path > /dev/null 2>&1 || true");
$commands->push("rmdir $fs_path > /dev/null 2>&1 || true");
$commands->push("rm -rf $path > /dev/null 2>&1 || true");
$commands->push("rmdir $path > /dev/null 2>&1 || true");
}
}
if ($commands->count() > 0) {
@ -55,6 +84,7 @@ public function deleteStorageOnServer()
public function saveStorageOnServer()
{
$this->load(['service']);
$isService = data_get($this->resource, 'service');
if ($isService) {
$workdir = $this->resource->service->workdir();
@ -74,30 +104,36 @@ public function saveStorageOnServer()
$commands->push("mkdir -p $parent_dir > /dev/null 2>&1 || true");
}
}
$fileVolume = $this;
$path = str(data_get($fileVolume, 'fs_path'));
$content = data_get($fileVolume, 'content');
$path = data_get_str($this, 'fs_path');
$content = data_get($this, 'content');
if ($path->startsWith('.')) {
$path = $path->after('.');
$path = $workdir.$path;
}
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
if ($isFile == 'OK' && $fileVolume->is_directory) {
if ($isFile == 'OK' && $this->is_directory) {
$content = instant_remote_process(["cat $path"], $server, false);
$fileVolume->is_directory = false;
$fileVolume->content = $content;
$fileVolume->save();
$this->is_directory = false;
$this->content = $content;
$this->save();
FileStorageChanged::dispatch(data_get($server, 'team_id'));
throw new \Exception('The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.');
} elseif ($isDir == 'OK' && ! $fileVolume->is_directory) {
$fileVolume->is_directory = true;
$fileVolume->save();
throw new \Exception('The following file is a directory on the server, but you are trying to mark it as a file. <br><br>Please delete the directory on the server or mark it as directory.');
} elseif ($isDir == 'OK' && ! $this->is_directory) {
if ($path == '/' || $path == '.' || $path == '..' || $path == '' || str($path)->isEmpty() || is_null($path)) {
$this->is_directory = true;
$this->save();
throw new \Exception('The following file is a directory on the server, but you are trying to mark it as a file. <br><br>Please delete the directory on the server or mark it as directory.');
}
instant_remote_process([
"rm -fr $path",
"touch $path",
], $server, false);
FileStorageChanged::dispatch(data_get($server, 'team_id'));
}
if ($isDir == 'NOK' && ! $fileVolume->is_directory) {
$chmod = data_get($fileVolume, 'chmod');
$chown = data_get($fileVolume, 'chown');
if ($isDir == 'NOK' && ! $this->is_directory) {
$chmod = data_get($this, 'chmod');
$chown = data_get($this, 'chown');
if ($content) {
$content = base64_encode($content);
$commands->push("echo '$content' | base64 -d | tee $path > /dev/null");
@ -111,7 +147,7 @@ public function saveStorageOnServer()
if ($chmod) {
$commands->push("chmod $chmod $path");
}
} elseif ($isDir == 'NOK' && $fileVolume->is_directory) {
} elseif ($isDir == 'NOK' && $this->is_directory) {
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
}

View File

@ -794,7 +794,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
$topLevelVolumes = collect($tempTopLevelVolumes);
}
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $allServices) {
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS, $resource, $allServices, $topLevelVolumes) {
// Workarounds for beta users.
if ($serviceName === 'registry') {
$tempServiceName = 'docker-registry';
@ -963,102 +963,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
// Collect/create/update volumes
if ($serviceVolumes->count() > 0) {
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($savedService, $topLevelVolumes) {
$type = null;
$source = null;
$target = null;
$content = null;
$isDirectory = false;
if (is_string($volume)) {
$source = str($volume)->before(':');
$target = str($volume)->after(':')->beforeLast(':');
if ($source->startsWith('./') || $source->startsWith('/') || $source->startsWith('~')) {
$type = str('bind');
// 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 = $savedService->fileStorages()->whereMountPath($target)->first();
if ($foundConfig) {
$contentNotNull = data_get($foundConfig, 'content');
if ($contentNotNull) {
$content = $contentNotNull;
}
$isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null);
}
if (is_null($isDirectory) && is_null($content)) {
// if isDirectory is not set & content is also not set, we assume it is a directory
ray('setting isDirectory to true');
$isDirectory = true;
}
}
if ($type?->value() === 'bind') {
if ($source->value() === '/var/run/docker.sock') {
return $volume;
}
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
return $volume;
}
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),
]
);
} elseif ($type->value() === 'volume') {
if ($topLevelVolumes->has($source->value())) {
$v = $topLevelVolumes->get($source->value());
if (data_get($v, 'driver_opts.type') === 'cifs') {
return $volume;
}
}
$slugWithoutUuid = Str::slug($source, '-');
$name = "{$savedService->service->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);
}
$topLevelVolumes->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));
return $volume;
});
$serviceVolumes = parseServiceVolumes($serviceVolumes, $savedService, $topLevelVolumes);
data_set($service, 'volumes', $serviceVolumes->toArray());
}
@ -1645,131 +1550,261 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
} elseif ($resource->compose_parsing_version === '2') {
if (count($serviceVolumes) > 0) {
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) {
if (is_string($volume)) {
$volume = str($volume);
if ($volume->contains(':') && ! $volume->startsWith('/')) {
$name = $volume->before(':');
$mount = $volume->after(':');
if ($name->startsWith('.') || $name->startsWith('~')) {
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
if ($name->startsWith('.')) {
$name = $name->replaceFirst('.', $dir);
}
if ($name->startsWith('~')) {
$name = $name->replaceFirst('~', $dir);
}
if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id";
}
$volume = str("$name:$mount");
} else {
if ($pull_request_id !== 0) {
$uuid = $resource->uuid;
$name = $uuid."-$name-pr-$pull_request_id";
$volume = str("$name:$mount");
if ($topLevelVolumes->has($name)) {
$v = $topLevelVolumes->get($name);
if (data_get($v, 'driver_opts.type') === 'cifs') {
// Do nothing
} else {
if (is_null(data_get($v, 'name'))) {
data_set($v, 'name', $name);
data_set($topLevelVolumes, $name, $v);
}
}
} else {
$topLevelVolumes->put($name, [
'name' => $name,
]);
}
} else {
$uuid = $resource->uuid;
$name = str($uuid."-$name");
$volume = str("$name:$mount");
if ($topLevelVolumes->has($name->value())) {
$v = $topLevelVolumes->get($name->value());
if (data_get($v, 'driver_opts.type') === 'cifs') {
// Do nothing
} else {
if (is_null(data_get($v, 'name'))) {
data_set($topLevelVolumes, $name->value(), $v);
}
}
} else {
$topLevelVolumes->put($name->value(), [
'name' => $name->value(),
]);
}
}
}
} else {
if ($volume->startsWith('/')) {
$name = $volume->before(':');
$mount = $volume->after(':');
if ($pull_request_id !== 0) {
$name = $name."-pr-$pull_request_id";
}
$volume = str("$name:$mount");
}
}
} elseif (is_array($volume)) {
$source = data_get($volume, 'source');
$target = data_get($volume, 'target');
$read_only = data_get($volume, 'read_only');
if ($source && $target) {
$uuid = $resource->uuid;
if ((str($source)->startsWith('.') || str($source)->startsWith('~') || str($source)->startsWith('/'))) {
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
if (str($source, '.')) {
$source = str($source)->replaceFirst('.', $dir);
}
if (str($source, '~')) {
$source = str($source)->replaceFirst('~', $dir);
}
if ($read_only) {
data_set($volume, 'source', $source.':'.$target.':ro');
} else {
data_set($volume, 'source', $source.':'.$target);
}
} else {
if ($pull_request_id === 0) {
$source = $uuid."-$source";
} else {
$source = $uuid."-$source-pr-$pull_request_id";
}
if ($read_only) {
data_set($volume, 'source', $source.':'.$target.':ro');
} else {
data_set($volume, 'source', $source.':'.$target);
}
if (! str($source)->startsWith('/')) {
if ($topLevelVolumes->has($source)) {
$v = $topLevelVolumes->get($source);
if (data_get($v, 'driver_opts.type') === 'cifs') {
// Do nothing
} else {
if (is_null(data_get($v, 'name'))) {
data_set($v, 'name', $source);
data_set($topLevelVolumes, $source, $v);
}
}
} else {
$topLevelVolumes->put($source, [
'name' => $source,
]);
}
}
}
}
}
if (is_array($volume)) {
return data_get($volume, 'source');
}
dispatch(new ServerFilesFromServerJob($resource));
$serviceVolumes = parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull_request_id);
ray($serviceVolumes);
return $volume->value();
});
data_set($service, 'volumes', $serviceVolumes->toArray());
// $serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) {
// if (is_string($volume)) {
// $volume = str($volume);
// if ($volume->contains(':')) {
// $name = $volume->before(':');
// $mount = $volume->after(':')->beforeLast(':');
// if ($name->startsWith('.') || $name->startsWith('~') || $name->startsWith('/')) {
// // File or dir mount from the host system
// $dir = base_configuration_dir().'/applications/'.$resource->uuid;
// if ($name->startsWith('.')) {
// $name = $name->replaceFirst('.', $dir);
// }
// if ($name->startsWith('~')) {
// $name = $name->replaceFirst('~', $dir);
// }
// if ($pull_request_id !== 0) {
// $name = $name."-pr-$pull_request_id";
// }
// $volume = str("$name:$mount");
// LocalFileVolume::updateOrCreate(
// [
// 'mount_path' => $mount,
// 'resource_id' => $resource->id,
// 'resource_type' => get_class($resource),
// ],
// [
// 'fs_path' => $name,
// 'mount_path' => $mount,
// 'is_directory' => true,
// 'resource_id' => $resource->id,
// 'resource_type' => get_class($resource),
// ]
// );
// } else {
// // Docker Volume part
// if ($pull_request_id == 0) {
// $uuid = $resource->uuid;
// $name = str($uuid."-$name");
// $volume = str("$name:$mount");
// if ($topLevelVolumes->has($name->value())) {
// $v = $topLevelVolumes->get($name->value());
// if (data_get($v, 'driver_opts.type') === 'cifs') {
// // Do nothing
// } else {
// if (is_null(data_get($v, 'name'))) {
// data_set($topLevelVolumes, $name->value(), $v);
// }
// }
// } else {
// $topLevelVolumes->put($name->value(), [
// 'name' => $name->value(),
// ]);
// }
// LocalPersistentVolume::updateOrCreate(
// [
// 'mount_path' => $mount,
// 'resource_id' => $resource->id,
// 'resource_type' => get_class($resource),
// ],
// [
// 'name' => $name,
// 'mount_path' => $mount,
// 'resource_id' => $resource->id,
// 'resource_type' => get_class($resource),
// ]
// );
// } else {
// $uuid = $resource->uuid;
// $name = $uuid."-$name-pr-$pull_request_id";
// $volume = str("$name:$mount");
// if ($topLevelVolumes->has($name)) {
// $v = $topLevelVolumes->get($name);
// if (data_get($v, 'driver_opts.type') === 'cifs') {
// // Do nothing
// } else {
// if (is_null(data_get($v, 'name'))) {
// data_set($v, 'name', $name);
// data_set($topLevelVolumes, $name, $v);
// }
// }
// } else {
// $topLevelVolumes->put($name, [
// 'name' => $name,
// ]);
// }
// LocalPersistentVolume::updateOrCreate(
// [
// 'mount_path' => $mount,
// 'resource_id' => $resource->id,
// 'resource_type' => get_class($resource),
// ],
// [
// 'name' => $name,
// 'mount_path' => $mount,
// 'resource_id' => $resource->id,
// 'resource_type' => get_class($resource),
// ]
// );
// }
// }
// }
// } elseif (is_array($volume)) {
// $source = data_get($volume, 'source');
// $target = data_get($volume, 'target');
// $type = data_get($volume, 'type');
// $read_only = data_get($volume, 'read_only');
// $content = data_get_str($volume, 'content');
// if ($source && $target) {
// if ($type?->value() === 'bind') {
// if ($source->value() === '/var/run/docker.sock') {
// return $volume;
// }
// if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
// return $volume;
// }
// 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),
// ]
// );
// } elseif ($type->value() === 'volume') {
// if ($topLevelVolumes->has($source->value())) {
// $v = $topLevelVolumes->get($source->value());
// if (data_get($v, 'driver_opts.type') === 'cifs') {
// return $volume;
// }
// }
// $slugWithoutUuid = Str::slug($source, '-');
// $name = "{$savedService->service->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);
// }
// $topLevelVolumes->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),
// ]
// );
// }
// $uuid = $resource->uuid;
// if ((str($source)->startsWith('.') || str($source)->startsWith('~') || str($source)->startsWith('/'))) {
// $dir = base_configuration_dir().'/applications/'.$resource->uuid;
// if (str($source, '.')) {
// $source = str($source)->replaceFirst('.', $dir);
// }
// if (str($source, '~')) {
// $source = str($source)->replaceFirst('~', $dir);
// }
// if ($read_only) {
// data_set($volume, 'source', $source.':'.$target.':ro');
// } else {
// data_set($volume, 'source', $source.':'.$target);
// }
// } else {
// if ($pull_request_id === 0) {
// $source = $uuid."-$source";
// } else {
// $source = $uuid."-$source-pr-$pull_request_id";
// }
// if ($read_only) {
// data_set($volume, 'source', $source.':'.$target.':ro');
// } else {
// data_set($volume, 'source', $source.':'.$target);
// }
// if (! str($source)->startsWith('/')) {
// if ($topLevelVolumes->has($source)) {
// $v = $topLevelVolumes->get($source);
// if (data_get($v, 'driver_opts.type') === 'cifs') {
// // Do nothing
// } else {
// if (is_null(data_get($v, 'name'))) {
// data_set($v, 'name', $source);
// data_set($topLevelVolumes, $source, $v);
// }
// }
// } else {
// $topLevelVolumes->put($source, [
// 'name' => $source,
// ]);
// }
// }
// }
// if ($content->isNotEmpty()) {
// LocalFileVolume::updateOrCreate(
// [
// 'mount_path' => $target,
// 'resource_id' => $resource->id,
// 'resource_type' => get_class($resource),
// ],
// [
// 'fs_path' => $source,
// 'mount_path' => $target,
// 'content' => $content,
// 'is_directory' => false,
// 'resource_id' => $resource->id,
// 'resource_type' => get_class($resource),
// ]
// );
// } else {
// LocalFileVolume::updateOrCreate(
// [
// 'mount_path' => $target,
// 'resource_id' => $resource->id,
// 'resource_type' => get_class($resource),
// ],
// [
// 'fs_path' => $source,
// 'mount_path' => $target,
// 'is_directory' => true,
// 'resource_id' => $resource->id,
// 'resource_type' => get_class($resource),
// ]
// );
// }
// }
// }
// if (is_array($volume)) {
// return data_get($volume, 'source');
// }
// dispatch(new ServerFilesFromServerJob($resource));
// return $volume->value();
// });
data_set($service, 'volumes', $serviceVolumes->toArray());
}
}
@ -2662,3 +2697,124 @@ function customApiValidator(Collection|array $item, array $rules)
'required' => 'This field is required.',
]);
}
function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull_request_id = 0)
{
return $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) {
$type = null;
$source = null;
$target = null;
$content = null;
$isDirectory = false;
if (is_string($volume)) {
$source = str($volume)->before(':');
$target = str($volume)->after(':')->beforeLast(':');
if ($source->startsWith('./') || $source->startsWith('/') || $source->startsWith('~')) {
$type = str('bind');
// 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 = $resource->fileStorages()->whereMountPath($target)->first();
if ($foundConfig) {
$contentNotNull = data_get($foundConfig, 'content');
if ($contentNotNull) {
$content = $contentNotNull;
}
$isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null);
}
if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) {
// if isDirectory is not set (or false) & content is also not set, we assume it is a directory
ray('setting isDirectory to true');
$isDirectory = true;
}
}
if ($type?->value() === 'bind') {
if ($source->value() === '/var/run/docker.sock') {
return $volume;
}
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
return $volume;
}
if (get_class($resource) === "App\Models\Application") {
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
} else {
$dir = base_configuration_dir().'/services/'.$resource->service->uuid;
}
if ($source->startsWith('.')) {
$source = $source->replaceFirst('.', $dir);
}
if ($source->startsWith('~')) {
$source = $source->replaceFirst('~', $dir);
}
if ($pull_request_id !== 0) {
$source = $source."-pr-$pull_request_id";
}
$volume = str("$source:$target");
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $resource->id,
'resource_type' => get_class($resource),
],
[
'fs_path' => $source,
'mount_path' => $target,
'content' => $content,
'is_directory' => $isDirectory,
'resource_id' => $resource->id,
'resource_type' => get_class($resource),
]
);
} elseif ($type->value() === 'volume') {
if ($topLevelVolumes->has($source->value())) {
$v = $topLevelVolumes->get($source->value());
if (data_get($v, 'driver_opts.type') === 'cifs') {
return $volume;
}
}
$slugWithoutUuid = Str::slug($source, '-');
if (get_class($resource) === "App\Models\Application") {
$name = "{$resource->uuid}_{$slugWithoutUuid}";
} else {
$name = "{$resource->service->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);
}
$topLevelVolumes->put($name, [
'name' => $name,
]);
LocalPersistentVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $resource->id,
'resource_type' => get_class($resource),
],
[
'name' => $name,
'mount_path' => $target,
'resource_id' => $resource->id,
'resource_type' => get_class($resource),
]
);
}
dispatch(new ServerFilesFromServerJob($resource));
return str($volume)->value();
});
}

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('local_file_volumes', function (Blueprint $table) {
$table->boolean('is_based_on_git')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('local_file_volumes', function (Blueprint $table) {
$table->dropColumn('is_based_on_git');
});
}
};

View File

@ -10,6 +10,7 @@
@endif
<div>{{ $workdir }}{{ $fs_path }} -> {{ $fileStorage->mount_path }}</div>
</div>
<form wire:submit='submit' class="flex flex-col gap-2">
<div class="flex gap-2">
@if ($fileStorage->is_directory)
@ -26,25 +27,40 @@ class="text-error">Please think
</div>
</x-modal-confirmation>
@endif
<x-modal-confirmation isErrorButton buttonTitle="Delete">
<div class="px-2">This storage will be deleted. It is not reversible. <strong
class="text-error">Please
think
again.</strong><br><br></div>
<h4>Actions</h4>
@if ($fileStorage->is_directory)
<x-forms.checkbox id="permanently_delete"
label="Permanently delete directory from the server?"></x-forms.checkbox>
@else
<x-forms.checkbox id="permanently_delete"
label="Permanently delete file from the server?"></x-forms.checkbox>
@endif
</x-modal-confirmation>
@if (!$fileStorage->is_based_on_git)
<x-modal-confirmation isErrorButton buttonTitle="Delete">
<div class="px-2">This storage will be deleted. It is not reversible. <strong
class="text-error">Please
think
again.</strong><br><br></div>
<h4>Actions</h4>
@if ($fileStorage->is_directory)
<x-forms.checkbox id="permanently_delete"
label="Permanently delete directory from the server?"></x-forms.checkbox>
@else
<x-forms.checkbox id="permanently_delete"
label="Permanently delete file from the server?"></x-forms.checkbox>
@endif
</x-modal-confirmation>
@endif
</div>
@if (!$fileStorage->is_directory)
<x-forms.textarea label="Content" rows="20" id="fileStorage.content"></x-forms.textarea>
<x-forms.button class="w-full" type="submit">Save</x-forms.button>
@if (data_get($resource, 'settings.is_preserve_repository_enabled'))
<div class="w-96">
<x-forms.checkbox instantSave label="Is this based on the Git repository?"
id="fileStorage.is_based_on_git"></x-forms.checkbox>
</div>
@endif
<x-forms.textarea
label="{{ $fileStorage->is_based_on_git ? 'Content (refreshed after a successful deployment)' : 'Content' }}"
rows="20" id="fileStorage.content"
readonly="{{ $fileStorage->is_based_on_git }}"></x-forms.textarea>
@if (!$fileStorage->is_based_on_git)
<x-forms.button class="w-full" type="submit">Save</x-forms.button>
@endif
@endif
</form>
</div>