From d53065967ec56e81a5cef15a69fa7643c7580101 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 7 Jan 2025 17:18:04 +0100 Subject: [PATCH 01/19] fix: exclude system and computed fields from model replication - Remove auto-generated properties (`id`, `created_at` and `updated_at` ) from replicate - Exclude computed count properties (`additional_servers_count`, `additional_networks_count`) loaded by global scope to prevent errors --- app/Livewire/Project/CloneMe.php | 48 ++++++++++++++++--- .../Project/Shared/ResourceOperations.php | 48 ++++++++++++++++--- 2 files changed, 84 insertions(+), 12 deletions(-) diff --git a/app/Livewire/Project/CloneMe.php b/app/Livewire/Project/CloneMe.php index 593355c44..02cee30e1 100644 --- a/app/Livewire/Project/CloneMe.php +++ b/app/Livewire/Project/CloneMe.php @@ -109,7 +109,13 @@ class CloneMe extends Component $services = $this->environment->services; foreach ($applications as $application) { $uuid = (string) new Cuid2; - $newApplication = $application->replicate()->fill([ + $newApplication = $application->replicate([ + 'id', + 'created_at', + 'updated_at', + 'additional_servers_count', + 'additional_networks_count', + ])->fill([ 'uuid' => $uuid, 'fqdn' => generateFqdn($this->server, $uuid), 'status' => 'exited', @@ -120,14 +126,26 @@ class CloneMe extends Component $newApplication->save(); $environmentVaribles = $application->environment_variables()->get(); foreach ($environmentVaribles as $environmentVarible) { - $newEnvironmentVariable = $environmentVarible->replicate()->fill([ + $newEnvironmentVariable = $environmentVarible->replicate([ + 'id', + 'created_at', + 'updated_at', + 'additional_servers_count', + 'additional_networks_count', + ])->fill([ 'resourceable_id' => $newApplication->id, ]); $newEnvironmentVariable->save(); } $persistentVolumes = $application->persistentStorages()->get(); foreach ($persistentVolumes as $volume) { - $newPersistentVolume = $volume->replicate()->fill([ + $newPersistentVolume = $volume->replicate([ + 'id', + 'created_at', + 'updated_at', + 'additional_servers_count', + 'additional_networks_count', + ])->fill([ 'name' => $newApplication->uuid.'-'.str($volume->name)->afterLast('-'), 'resource_id' => $newApplication->id, ]); @@ -136,7 +154,13 @@ class CloneMe extends Component } foreach ($databases as $database) { $uuid = (string) new Cuid2; - $newDatabase = $database->replicate()->fill([ + $newDatabase = $database->replicate([ + 'id', + 'created_at', + 'updated_at', + 'additional_servers_count', + 'additional_networks_count', + ])->fill([ 'uuid' => $uuid, 'status' => 'exited', 'started_at' => null, @@ -149,13 +173,25 @@ class CloneMe extends Component $payload = []; $payload['resourceable_id'] = $newDatabase->id; $payload['resourceable_type'] = $newDatabase->getMorphClass(); - $newEnvironmentVariable = $environmentVarible->replicate()->fill($payload); + $newEnvironmentVariable = $environmentVarible->replicate([ + 'id', + 'created_at', + 'updated_at', + 'additional_servers_count', + 'additional_networks_count', + ])->fill($payload); $newEnvironmentVariable->save(); } } foreach ($services as $service) { $uuid = (string) new Cuid2; - $newService = $service->replicate()->fill([ + $newService = $service->replicate([ + 'id', + 'created_at', + 'updated_at', + 'additional_servers_count', + 'additional_networks_count', + ])->fill([ 'uuid' => $uuid, 'environment_id' => $environment->id, 'destination_id' => $this->selectedDestination, diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php index c6a32f0c7..0c79bf0aa 100644 --- a/app/Livewire/Project/Shared/ResourceOperations.php +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -44,7 +44,13 @@ class ResourceOperations extends Component if ($this->resource->getMorphClass() === \App\Models\Application::class) { $name = 'clone-of-'.str($this->resource->name)->limit(20).'-'.$uuid; - $new_resource = $this->resource->replicate()->fill([ + $new_resource = $this->resource->replicate([ + 'id', + 'created_at', + 'updated_at', + 'additional_servers_count', + 'additional_networks_count', + ])->fill([ 'uuid' => $uuid, 'name' => $name, 'fqdn' => generateFqdn($server, $uuid), @@ -59,7 +65,13 @@ class ResourceOperations extends Component } $environmentVaribles = $this->resource->environment_variables()->get(); foreach ($environmentVaribles as $environmentVarible) { - $newEnvironmentVariable = $environmentVarible->replicate()->fill([ + $newEnvironmentVariable = $environmentVarible->replicate([ + 'id', + 'created_at', + 'updated_at', + 'additional_servers_count', + 'additional_networks_count', + ])->fill([ 'resourceable_id' => $new_resource->id, 'resourceable_type' => $new_resource->getMorphClass(), ]); @@ -71,7 +83,13 @@ class ResourceOperations extends Component if ($volumeName === $volume->name) { $volumeName = $new_resource->uuid.'-'.str($volume->name)->afterLast('-'); } - $newPersistentVolume = $volume->replicate()->fill([ + $newPersistentVolume = $volume->replicate([ + 'id', + 'created_at', + 'updated_at', + 'additional_servers_count', + 'additional_networks_count', + ])->fill([ 'name' => $volumeName, 'resource_id' => $new_resource->id, ]); @@ -95,7 +113,13 @@ class ResourceOperations extends Component $this->resource->getMorphClass() === \App\Models\StandaloneClickhouse::class ) { $uuid = (string) new Cuid2; - $new_resource = $this->resource->replicate()->fill([ + $new_resource = $this->resource->replicate([ + 'id', + 'created_at', + 'updated_at', + 'additional_servers_count', + 'additional_networks_count', + ])->fill([ 'uuid' => $uuid, 'name' => $this->resource->name.'-clone-'.$uuid, 'status' => 'exited', @@ -117,7 +141,13 @@ class ResourceOperations extends Component } elseif ($this->resource->type() === 'standalone-mariadb') { $payload['standalone_mariadb_id'] = $new_resource->id; } - $newEnvironmentVariable = $environmentVarible->replicate()->fill($payload); + $newEnvironmentVariable = $environmentVarible->replicate([ + 'id', + 'created_at', + 'updated_at', + 'additional_servers_count', + 'additional_networks_count', + ])->fill($payload); $newEnvironmentVariable->save(); } $route = route('project.database.configuration', [ @@ -129,7 +159,13 @@ class ResourceOperations extends Component return redirect()->to($route); } elseif ($this->resource->type() === 'service') { $uuid = (string) new Cuid2; - $new_resource = $this->resource->replicate()->fill([ + $new_resource = $this->resource->replicate([ + 'id', + 'created_at', + 'updated_at', + 'additional_servers_count', + 'additional_networks_count', + ])->fill([ 'uuid' => $uuid, 'name' => $this->resource->name.'-clone-'.$uuid, 'destination_id' => $new_destination->id, From 91e12209128a8f95f33b147275ffb5af4c916bc5 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 7 Jan 2025 19:26:25 +0100 Subject: [PATCH 02/19] fix: service cloning on a separate server - To be able to clone a service to a new server we need to set the server_id as well, otherwise it will be cloned on the same server. --- app/Livewire/Project/Shared/ResourceOperations.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php index 0c79bf0aa..091ea0356 100644 --- a/app/Livewire/Project/Shared/ResourceOperations.php +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -30,7 +30,9 @@ class ResourceOperations extends Component $this->servers = currentTeam()->servers; } - public function cloneTo($destination_id) + public function cloneTo($destination_id) // issues is applications table stuff is replciated but not the application_settings table stuff and also application_preveiws is not replicated, Also only volumes but not files an directory mounts are cloned + + // Also the next issue is that the thing is not coloned to the correct server { $new_destination = StandaloneDocker::find($destination_id); if (! $new_destination) { @@ -41,6 +43,7 @@ class ResourceOperations extends Component } $uuid = (string) new Cuid2; $server = $new_destination->server; + if ($this->resource->getMorphClass() === \App\Models\Application::class) { $name = 'clone-of-'.str($this->resource->name)->limit(20).'-'.$uuid; @@ -69,7 +72,7 @@ class ResourceOperations extends Component 'id', 'created_at', 'updated_at', - 'additional_servers_count', + 'additional_servers_count', // not needed because it only is computed for the application 'additional_networks_count', ])->fill([ 'resourceable_id' => $new_resource->id, @@ -169,7 +172,10 @@ class ResourceOperations extends Component 'uuid' => $uuid, 'name' => $this->resource->name.'-clone-'.$uuid, 'destination_id' => $new_destination->id, + 'destination_type' => $new_destination->getMorphClass(), + 'server_id' => $new_destination->server_id, // server_id is probably not needed anymore because of the new polymorphic relationships (here it is needed for clone to work - but maybe we can drop the column) ]); + $new_resource->save(); foreach ($new_resource->applications() as $application) { $application->update([ From b77a223ec92048ea95943b3035e44e0be2d8f1b6 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Tue, 7 Jan 2025 20:27:54 +0100 Subject: [PATCH 03/19] fix: Application cloning - disable URL auto generation - clone Application settings as well - clone tags - clone scheduled tasks - clone preview deployment settings - clone file and directory mounts --- app/Livewire/Project/CloneMe.php | 83 +++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 17 deletions(-) diff --git a/app/Livewire/Project/CloneMe.php b/app/Livewire/Project/CloneMe.php index 02cee30e1..00d8f32fd 100644 --- a/app/Livewire/Project/CloneMe.php +++ b/app/Livewire/Project/CloneMe.php @@ -117,49 +117,102 @@ class CloneMe extends Component 'additional_networks_count', ])->fill([ 'uuid' => $uuid, - 'fqdn' => generateFqdn($this->server, $uuid), + // 'fqdn' => generateFqdn($this->server, $uuid), - Proxy labels are also duplicated, so can we duplicate the domain as well? 'status' => 'exited', 'environment_id' => $environment->id, - // This is not correct, but we need to set it to something 'destination_id' => $this->selectedDestination, ]); $newApplication->save(); - $environmentVaribles = $application->environment_variables()->get(); - foreach ($environmentVaribles as $environmentVarible) { - $newEnvironmentVariable = $environmentVarible->replicate([ + + $newApplication->settings()->delete(); // first delete the automatically created settings and fill in the old ones (to properly clone and avoid duplicates) + $applicationSettings = $application->settings; + if ($applicationSettings) { + $newApplicationSettings = $applicationSettings->replicate([ 'id', 'created_at', 'updated_at', - 'additional_servers_count', - 'additional_networks_count', ])->fill([ - 'resourceable_id' => $newApplication->id, + 'application_id' => $newApplication->id, ]); - $newEnvironmentVariable->save(); + $newApplicationSettings->save(); } + + $tags = $application->tags; + foreach ($tags as $tag) { + $newApplication->tags()->attach($tag->id); + } + + $scheduledTasks = $application->scheduled_tasks()->get(); + foreach ($scheduledTasks as $task) { + $newTask = $task->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'uuid' => (string) new Cuid2, + 'application_id' => $newApplication->id, + 'team_id' => currentTeam()->id, + ]); + $newTask->save(); + } + + $applicationPreviews = $application->previews()->get(); + foreach ($applicationPreviews as $preview) { + $newPreview = $preview->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'application_id' => $newApplication->id, + 'status' => 'exited', + ]); + $newPreview->save(); + } + $persistentVolumes = $application->persistentStorages()->get(); foreach ($persistentVolumes as $volume) { $newPersistentVolume = $volume->replicate([ 'id', 'created_at', 'updated_at', - 'additional_servers_count', - 'additional_networks_count', ])->fill([ 'name' => $newApplication->uuid.'-'.str($volume->name)->afterLast('-'), 'resource_id' => $newApplication->id, ]); $newPersistentVolume->save(); } + + $fileStorages = $application->fileStorages()->get(); + foreach ($fileStorages as $storage) { + $newStorage = $storage->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'resource_id' => $newApplication->id, + ]); + $newStorage->save(); + } + + $environmentVaribles = $application->environment_variables()->get(); + foreach ($environmentVaribles as $environmentVarible) { + $newEnvironmentVariable = $environmentVarible->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'resourceable_id' => $newApplication->id, + ]); + $newEnvironmentVariable->save(); + } } + foreach ($databases as $database) { $uuid = (string) new Cuid2; $newDatabase = $database->replicate([ 'id', 'created_at', 'updated_at', - 'additional_servers_count', - 'additional_networks_count', ])->fill([ 'uuid' => $uuid, 'status' => 'exited', @@ -177,8 +230,6 @@ class CloneMe extends Component 'id', 'created_at', 'updated_at', - 'additional_servers_count', - 'additional_networks_count', ])->fill($payload); $newEnvironmentVariable->save(); } @@ -189,8 +240,6 @@ class CloneMe extends Component 'id', 'created_at', 'updated_at', - 'additional_servers_count', - 'additional_networks_count', ])->fill([ 'uuid' => $uuid, 'environment_id' => $environment->id, From 66d0926142356813c26871166e156278379ded73 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:00:49 +0100 Subject: [PATCH 04/19] fix: `Undefined variable $fs_path` for databases --- app/Livewire/Project/Shared/Storages/Add.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/Livewire/Project/Shared/Storages/Add.php b/app/Livewire/Project/Shared/Storages/Add.php index 6e250bd90..dc015386c 100644 --- a/app/Livewire/Project/Shared/Storages/Add.php +++ b/app/Livewire/Project/Shared/Storages/Add.php @@ -81,11 +81,18 @@ class Add extends Component 'file_storage_path' => 'string', 'file_storage_content' => 'nullable|string', ]); + $this->file_storage_path = trim($this->file_storage_path); $this->file_storage_path = str($this->file_storage_path)->start('/')->value(); + if ($this->resource->getMorphClass() === \App\Models\Application::class) { $fs_path = application_configuration_dir().'/'.$this->resource->uuid.$this->file_storage_path; + } elseif (str($this->resource->getMorphClass())->contains('Standalone')) { + $fs_path = database_configuration_dir().'/'.$this->resource->uuid.$this->file_storage_path; + } else { + throw new \Exception('No valid resource type for file mount storage type!'); } + LocalFileVolume::create( [ 'fs_path' => $fs_path, @@ -109,10 +116,12 @@ class Add extends Component 'file_storage_directory_source' => 'string', 'file_storage_directory_destination' => 'string', ]); + $this->file_storage_directory_source = trim($this->file_storage_directory_source); $this->file_storage_directory_source = str($this->file_storage_directory_source)->start('/')->value(); $this->file_storage_directory_destination = trim($this->file_storage_directory_destination); $this->file_storage_directory_destination = str($this->file_storage_directory_destination)->start('/')->value(); + LocalFileVolume::create( [ 'fs_path' => $this->file_storage_directory_source, From d45e2d18b7ecfab14bf0198f664a6f28aeda1c03 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:26:02 +0100 Subject: [PATCH 05/19] fix: Service and database cloning and label generation Databases: - clone tags - clone volumes - clone file and directory mounts - clone backup schedules Services: - clone tags - clone scheduled task - clone environment variables --- app/Livewire/Project/CloneMe.php | 120 +++++++++++++++++++++++++++++-- 1 file changed, 113 insertions(+), 7 deletions(-) diff --git a/app/Livewire/Project/CloneMe.php b/app/Livewire/Project/CloneMe.php index 00d8f32fd..8275b64c6 100644 --- a/app/Livewire/Project/CloneMe.php +++ b/app/Livewire/Project/CloneMe.php @@ -117,14 +117,20 @@ class CloneMe extends Component 'additional_networks_count', ])->fill([ 'uuid' => $uuid, - // 'fqdn' => generateFqdn($this->server, $uuid), - Proxy labels are also duplicated, so can we duplicate the domain as well? + 'fqdn' => generateFqdn($this->server, $uuid), // this also needs a condition 'status' => 'exited', 'environment_id' => $environment->id, 'destination_id' => $this->selectedDestination, ]); $newApplication->save(); - $newApplication->settings()->delete(); // first delete the automatically created settings and fill in the old ones (to properly clone and avoid duplicates) + if ($newApplication->destination->server->proxyType() !== 'NONE' || ! $newApplication->application_settings->is_container_label_readonly_enabled) { // fix after switching this logic up + $customLabels = str(implode('|coolify|', generateLabelsApplication($newApplication)))->replace('|coolify|', "\n"); + $newApplication->custom_labels = base64_encode($customLabels); + $newApplication->save(); + } + + $newApplication->settings()->delete(); $applicationSettings = $application->settings; if ($applicationSettings) { $newApplicationSettings = $applicationSettings->replicate([ @@ -221,6 +227,65 @@ class CloneMe extends Component 'destination_id' => $this->selectedDestination, ]); $newDatabase->save(); + + $tags = $database->tags; + foreach ($tags as $tag) { + $newDatabase->tags()->attach($tag->id); + } + + $newDatabase->persistentStorages()->delete(); + $persistentVolumes = $database->persistentStorages()->get(); + foreach ($persistentVolumes as $volume) { + $originalName = $volume->name; + $newName = ''; + + if (str_starts_with($originalName, 'postgres-data-')) { + $newName = 'postgres-data-'.$newDatabase->uuid; + } else { + $newName = str($originalName) + ->replaceFirst($database->uuid, $newDatabase->uuid) + ->toString(); + } + + $newPersistentVolume = $volume->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'name' => $newName, + 'resource_id' => $newDatabase->id, + ]); + $newPersistentVolume->save(); + } + + $fileStorages = $database->fileStorages()->get(); + foreach ($fileStorages as $storage) { + $newStorage = $storage->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'resource_id' => $newDatabase->id, + ]); + $newStorage->save(); + } + + $scheduledBackups = $database->scheduledBackups()->get(); + foreach ($scheduledBackups as $backup) { + $uuid = (string) new Cuid2; + $newBackup = $backup->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'uuid' => $uuid, + 'database_id' => $newDatabase->id, + 'database_type' => $newDatabase->getMorphClass(), + 'team_id' => currentTeam()->id, + ]); + $newBackup->save(); + } + $environmentVaribles = $database->environment_variables()->get(); foreach ($environmentVaribles as $environmentVarible) { $payload = []; @@ -234,6 +299,7 @@ class CloneMe extends Component $newEnvironmentVariable->save(); } } + foreach ($services as $service) { $uuid = (string) new Cuid2; $newService = $service->replicate([ @@ -246,25 +312,65 @@ class CloneMe extends Component 'destination_id' => $this->selectedDestination, ]); $newService->save(); + + $tags = $service->tags; + foreach ($tags as $tag) { + $newService->tags()->attach($tag->id); + } + + $scheduledTasks = $service->scheduled_tasks()->get(); + foreach ($scheduledTasks as $task) { + $newTask = $task->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'uuid' => (string) new Cuid2, + 'service_id' => $newService->id, + 'team_id' => currentTeam()->id, + ]); + $newTask->save(); + } + + $environmentVariables = $service->environment_variables()->get(); + foreach ($environmentVariables as $environmentVariable) { + $newEnvironmentVariable = $environmentVariable->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'resourceable_id' => $newService->id, + 'resourceable_type' => $newService->getMorphClass(), + ]); + $newEnvironmentVariable->save(); + } + foreach ($newService->applications() as $application) { $application->update([ 'status' => 'exited', ]); } + foreach ($newService->databases() as $database) { $database->update([ 'status' => 'exited', ]); } + $newService->parse(); } - return redirect()->route('project.resource.index', [ - 'project_uuid' => $project->uuid, - 'environment_uuid' => $environment->uuid, - ]); } catch (\Exception $e) { - return handleError($e, $this); + handleError($e, $this); + + return; + } finally { + if (! isset($e)) { + return redirect()->route('project.resource.index', [ + 'project_uuid' => $project->uuid, + 'environment_uuid' => $environment->uuid, + ]); + } } } } From 3d4e8b98673507df247fd34d8fe8941ce9048ad3 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 8 Jan 2025 17:05:11 +0100 Subject: [PATCH 06/19] chore: switch up readonly lables to make more sense --- .../Api/ApplicationsController.php | 10 ++--- app/Jobs/ApplicationDeploymentJob.php | 2 +- app/Livewire/Project/Application/General.php | 2 +- ...01_08_154008_switch_up_readonly_labels.php | 39 +++++++++++++++++++ .../project/application/general.blade.php | 4 +- 5 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 database/migrations/2025_01_08_154008_switch_up_readonly_labels.php diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 4df187c19..5265fbb37 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -863,7 +863,7 @@ class ApplicationsController extends Controller $application->settings->save(); } $application->refresh(); - if (! $application->settings->is_container_label_readonly_enabled) { + if ($application->settings->is_container_label_readonly_enabled) { $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); $application->save(); } @@ -964,7 +964,7 @@ class ApplicationsController extends Controller $application->settings->is_build_server_enabled = $useBuildServer; $application->settings->save(); } - if (! $application->settings->is_container_label_readonly_enabled) { + if ($application->settings->is_container_label_readonly_enabled) { $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); $application->save(); } @@ -1061,7 +1061,7 @@ class ApplicationsController extends Controller $application->settings->is_build_server_enabled = $useBuildServer; $application->settings->save(); } - if (! $application->settings->is_container_label_readonly_enabled) { + if ($application->settings->is_container_label_readonly_enabled) { $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); $application->save(); } @@ -1150,7 +1150,7 @@ class ApplicationsController extends Controller $application->settings->is_build_server_enabled = $useBuildServer; $application->settings->save(); } - if (! $application->settings->is_container_label_readonly_enabled) { + if ($application->settings->is_container_label_readonly_enabled) { $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); $application->save(); } @@ -1214,7 +1214,7 @@ class ApplicationsController extends Controller $application->settings->is_build_server_enabled = $useBuildServer; $application->settings->save(); } - if (! $application->settings->is_container_label_readonly_enabled) { + if ($application->settings->is_container_label_readonly_enabled) { $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); $application->save(); } diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index addaf436a..4308f0f68 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1683,7 +1683,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $this->application->custom_labels = base64_encode($labels->implode("\n")); $this->application->save(); } else { - if (! $this->application->settings->is_container_label_readonly_enabled) { + if ($this->application->settings->is_container_label_readonly_enabled) { $labels = collect(generateLabelsApplication($this->application, $this->preview)); } } diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index d1cc3476c..576f87801 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -153,7 +153,7 @@ class General extends Component $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) { + if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && $this->application->settings->is_container_label_readonly_enabled === true) { $this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n"); $this->application->custom_labels = base64_encode($this->customLabels); $this->application->save(); diff --git a/database/migrations/2025_01_08_154008_switch_up_readonly_labels.php b/database/migrations/2025_01_08_154008_switch_up_readonly_labels.php new file mode 100644 index 000000000..aae089d9e --- /dev/null +++ b/database/migrations/2025_01_08_154008_switch_up_readonly_labels.php @@ -0,0 +1,39 @@ +update([ + 'is_container_label_readonly_enabled' => DB::raw('NOT is_container_label_readonly_enabled'), + ]); + + Schema::table('application_settings', function (Blueprint $table) { + $table->boolean('is_container_label_readonly_enabled')->default(true)->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + DB::table('application_settings') + ->update([ + 'is_container_label_readonly_enabled' => DB::raw('NOT is_container_label_readonly_enabled'), + ]); + + Schema::table('application_settings', function (Blueprint $table) { + $table->boolean('is_container_label_readonly_enabled')->default(false)->change(); + }); + } +}; diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index bdcde8deb..38fe8dbeb 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -284,7 +284,7 @@ helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.

If you want to use env variables inside the labels, turn this off." id="application.settings.is_container_label_escape_enabled" instantSave> @endif @@ -315,7 +315,7 @@ helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.

If you want to use env variables inside the labels, turn this off." id="application.settings.is_container_label_escape_enabled" instantSave> Date: Wed, 8 Jan 2025 17:07:20 +0100 Subject: [PATCH 07/19] fix: labels and URL generation when cloning --- app/Livewire/Project/CloneMe.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/Livewire/Project/CloneMe.php b/app/Livewire/Project/CloneMe.php index 8275b64c6..2e3e11a41 100644 --- a/app/Livewire/Project/CloneMe.php +++ b/app/Livewire/Project/CloneMe.php @@ -108,7 +108,14 @@ class CloneMe extends Component $databases = $this->environment->databases(); $services = $this->environment->services; foreach ($applications as $application) { + $applicationSettings = $application->settings; + $uuid = (string) new Cuid2; + $url = $application->fqdn; + if ($this->server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) { + $url = generateFqdn($this->server, $uuid); + } + $newApplication = $application->replicate([ 'id', 'created_at', @@ -117,21 +124,20 @@ class CloneMe extends Component 'additional_networks_count', ])->fill([ 'uuid' => $uuid, - 'fqdn' => generateFqdn($this->server, $uuid), // this also needs a condition + 'fqdn' => $url, 'status' => 'exited', 'environment_id' => $environment->id, 'destination_id' => $this->selectedDestination, ]); $newApplication->save(); - if ($newApplication->destination->server->proxyType() !== 'NONE' || ! $newApplication->application_settings->is_container_label_readonly_enabled) { // fix after switching this logic up + if ($newApplication->destination->server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) { $customLabels = str(implode('|coolify|', generateLabelsApplication($newApplication)))->replace('|coolify|', "\n"); $newApplication->custom_labels = base64_encode($customLabels); $newApplication->save(); } $newApplication->settings()->delete(); - $applicationSettings = $application->settings; if ($applicationSettings) { $newApplicationSettings = $applicationSettings->replicate([ 'id', From fc1963f642495cdb904202bd6d997755887ee28c Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 8 Jan 2025 17:08:36 +0100 Subject: [PATCH 08/19] chore: remove unused computed fields --- app/Livewire/Project/Shared/ResourceOperations.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php index 091ea0356..635bf2b69 100644 --- a/app/Livewire/Project/Shared/ResourceOperations.php +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -72,8 +72,6 @@ class ResourceOperations extends Component 'id', 'created_at', 'updated_at', - 'additional_servers_count', // not needed because it only is computed for the application - 'additional_networks_count', ])->fill([ 'resourceable_id' => $new_resource->id, 'resourceable_type' => $new_resource->getMorphClass(), @@ -90,8 +88,6 @@ class ResourceOperations extends Component 'id', 'created_at', 'updated_at', - 'additional_servers_count', - 'additional_networks_count', ])->fill([ 'name' => $volumeName, 'resource_id' => $new_resource->id, @@ -120,8 +116,6 @@ class ResourceOperations extends Component 'id', 'created_at', 'updated_at', - 'additional_servers_count', - 'additional_networks_count', ])->fill([ 'uuid' => $uuid, 'name' => $this->resource->name.'-clone-'.$uuid, @@ -148,8 +142,6 @@ class ResourceOperations extends Component 'id', 'created_at', 'updated_at', - 'additional_servers_count', - 'additional_networks_count', ])->fill($payload); $newEnvironmentVariable->save(); } @@ -166,8 +158,6 @@ class ResourceOperations extends Component 'id', 'created_at', 'updated_at', - 'additional_servers_count', - 'additional_networks_count', ])->fill([ 'uuid' => $uuid, 'name' => $this->resource->name.'-clone-'.$uuid, From 3868a4c5e680f5f52cf604aabd9e09c1bd22c6c2 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 8 Jan 2025 17:26:18 +0100 Subject: [PATCH 09/19] fix: clone naming for different database data volumes --- app/Livewire/Project/CloneMe.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/Livewire/Project/CloneMe.php b/app/Livewire/Project/CloneMe.php index 2e3e11a41..5c577b24a 100644 --- a/app/Livewire/Project/CloneMe.php +++ b/app/Livewire/Project/CloneMe.php @@ -247,6 +247,20 @@ class CloneMe extends Component if (str_starts_with($originalName, 'postgres-data-')) { $newName = 'postgres-data-'.$newDatabase->uuid; + } elseif (str_starts_with($originalName, 'mysql-data-')) { + $newName = 'mysql-data-'.$newDatabase->uuid; + } elseif (str_starts_with($originalName, 'redis-data-')) { + $newName = 'redis-data-'.$newDatabase->uuid; + } elseif (str_starts_with($originalName, 'clickhouse-data-')) { + $newName = 'clickhouse-data-'.$newDatabase->uuid; + } elseif (str_starts_with($originalName, 'mariadb-data-')) { + $newName = 'mariadb-data-'.$newDatabase->uuid; + } elseif (str_starts_with($originalName, 'mongodb-data-')) { + $newName = 'mongodb-data-'.$newDatabase->uuid; + } elseif (str_starts_with($originalName, 'keydb-data-')) { + $newName = 'keydb-data-'.$newDatabase->uuid; + } elseif (str_starts_with($originalName, 'dragonfly-data-')) { + $newName = 'dragonfly-data-'.$newDatabase->uuid; } else { $newName = str($originalName) ->replaceFirst($database->uuid, $newDatabase->uuid) From 249e39ea7193f721ec2733159fe901742ad4abd5 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 8 Jan 2025 17:26:59 +0100 Subject: [PATCH 10/19] fix: implement all the cloneMe changes for ResourceOperations as well --- .../Project/Shared/ResourceOperations.php | 212 ++++++++++++++++-- 1 file changed, 189 insertions(+), 23 deletions(-) diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php index 635bf2b69..cb9f7db85 100644 --- a/app/Livewire/Project/Shared/ResourceOperations.php +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -30,9 +30,7 @@ class ResourceOperations extends Component $this->servers = currentTeam()->servers; } - public function cloneTo($destination_id) // issues is applications table stuff is replciated but not the application_settings table stuff and also application_preveiws is not replicated, Also only volumes but not files an directory mounts are cloned - - // Also the next issue is that the thing is not coloned to the correct server + public function cloneTo($destination_id) { $new_destination = StandaloneDocker::find($destination_id); if (! $new_destination) { @@ -46,6 +44,12 @@ class ResourceOperations extends Component if ($this->resource->getMorphClass() === \App\Models\Application::class) { $name = 'clone-of-'.str($this->resource->name)->limit(20).'-'.$uuid; + $applicationSettings = $this->resource->settings; + $url = $this->resource->fqdn; + + if ($server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) { + $url = generateFqdn($server, $uuid); + } $new_resource = $this->resource->replicate([ 'id', @@ -56,28 +60,62 @@ class ResourceOperations extends Component ])->fill([ 'uuid' => $uuid, 'name' => $name, - 'fqdn' => generateFqdn($server, $uuid), + 'fqdn' => $url, 'status' => 'exited', 'destination_id' => $new_destination->id, ]); $new_resource->save(); - if ($new_resource->destination->server->proxyType() !== 'NONE') { + + if ($new_resource->destination->server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) { $customLabels = str(implode('|coolify|', generateLabelsApplication($new_resource)))->replace('|coolify|', "\n"); $new_resource->custom_labels = base64_encode($customLabels); $new_resource->save(); } - $environmentVaribles = $this->resource->environment_variables()->get(); - foreach ($environmentVaribles as $environmentVarible) { - $newEnvironmentVariable = $environmentVarible->replicate([ + + $new_resource->settings()->delete(); + if ($applicationSettings) { + $newApplicationSettings = $applicationSettings->replicate([ 'id', 'created_at', 'updated_at', ])->fill([ - 'resourceable_id' => $new_resource->id, - 'resourceable_type' => $new_resource->getMorphClass(), + 'application_id' => $new_resource->id, ]); - $newEnvironmentVariable->save(); + $newApplicationSettings->save(); } + + $tags = $this->resource->tags; + foreach ($tags as $tag) { + $new_resource->tags()->attach($tag->id); + } + + $scheduledTasks = $this->resource->scheduled_tasks()->get(); + foreach ($scheduledTasks as $task) { + $newTask = $task->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'uuid' => (string) new Cuid2, + 'application_id' => $new_resource->id, + 'team_id' => currentTeam()->id, + ]); + $newTask->save(); + } + + $applicationPreviews = $this->resource->previews()->get(); + foreach ($applicationPreviews as $preview) { + $newPreview = $preview->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'application_id' => $new_resource->id, + 'status' => 'exited', + ]); + $newPreview->save(); + } + $persistentVolumes = $this->resource->persistentStorages()->get(); foreach ($persistentVolumes as $volume) { $volumeName = str($volume->name)->replace($this->resource->uuid, $new_resource->uuid)->value(); @@ -94,6 +132,32 @@ class ResourceOperations extends Component ]); $newPersistentVolume->save(); } + + $fileStorages = $this->resource->fileStorages()->get(); + foreach ($fileStorages as $storage) { + $newStorage = $storage->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'resource_id' => $new_resource->id, + ]); + $newStorage->save(); + } + + $environmentVaribles = $this->resource->environment_variables()->get(); + foreach ($environmentVaribles as $environmentVarible) { + $newEnvironmentVariable = $environmentVarible->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'resourceable_id' => $new_resource->id, + 'resourceable_type' => $new_resource->getMorphClass(), + ]); + $newEnvironmentVariable->save(); + } + $route = route('project.application.configuration', [ 'project_uuid' => $this->projectUuid, 'environment_uuid' => $this->environmentUuid, @@ -124,20 +188,85 @@ class ResourceOperations extends Component 'destination_id' => $new_destination->id, ]); $new_resource->save(); + + $tags = $this->resource->tags; + foreach ($tags as $tag) { + $new_resource->tags()->attach($tag->id); + } + + $new_resource->persistentStorages()->delete(); + $persistentVolumes = $this->resource->persistentStorages()->get(); + foreach ($persistentVolumes as $volume) { + $originalName = $volume->name; + $newName = ''; + + if (str_starts_with($originalName, 'postgres-data-')) { + $newName = 'postgres-data-'.$new_resource->uuid; + } elseif (str_starts_with($originalName, 'mysql-data-')) { + $newName = 'mysql-data-'.$new_resource->uuid; + } elseif (str_starts_with($originalName, 'redis-data-')) { + $newName = 'redis-data-'.$new_resource->uuid; + } elseif (str_starts_with($originalName, 'clickhouse-data-')) { + $newName = 'clickhouse-data-'.$new_resource->uuid; + } elseif (str_starts_with($originalName, 'mariadb-data-')) { + $newName = 'mariadb-data-'.$new_resource->uuid; + } elseif (str_starts_with($originalName, 'mongodb-data-')) { + $newName = 'mongodb-data-'.$new_resource->uuid; + } elseif (str_starts_with($originalName, 'keydb-data-')) { + $newName = 'keydb-data-'.$new_resource->uuid; + } elseif (str_starts_with($originalName, 'dragonfly-data-')) { + $newName = 'dragonfly-data-'.$new_resource->uuid; + } else { + $newName = str($originalName) + ->replaceFirst($this->resource->uuid, $new_resource->uuid) + ->toString(); + } + + $newPersistentVolume = $volume->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'name' => $newName, + 'resource_id' => $new_resource->id, + ]); + $newPersistentVolume->save(); + } + + $fileStorages = $this->resource->fileStorages()->get(); + foreach ($fileStorages as $storage) { + $newStorage = $storage->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'resource_id' => $new_resource->id, + ]); + $newStorage->save(); + } + + $scheduledBackups = $this->resource->scheduledBackups()->get(); + foreach ($scheduledBackups as $backup) { + $uuid = (string) new Cuid2; + $newBackup = $backup->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'uuid' => $uuid, + 'database_id' => $new_resource->id, + 'database_type' => $new_resource->getMorphClass(), + 'team_id' => currentTeam()->id, + ]); + $newBackup->save(); + } + $environmentVaribles = $this->resource->environment_variables()->get(); foreach ($environmentVaribles as $environmentVarible) { - $payload = []; - if ($this->resource->type() === 'standalone-postgresql') { - $payload['standalone_postgresql_id'] = $new_resource->id; - } elseif ($this->resource->type() === 'standalone-redis') { - $payload['standalone_redis_id'] = $new_resource->id; - } elseif ($this->resource->type() === 'standalone-mongodb') { - $payload['standalone_mongodb_id'] = $new_resource->id; - } elseif ($this->resource->type() === 'standalone-mysql') { - $payload['standalone_mysql_id'] = $new_resource->id; - } elseif ($this->resource->type() === 'standalone-mariadb') { - $payload['standalone_mariadb_id'] = $new_resource->id; - } + $payload = [ + 'resourceable_id' => $new_resource->id, + 'resourceable_type' => $new_resource->getMorphClass(), + ]; $newEnvironmentVariable = $environmentVarible->replicate([ 'id', 'created_at', @@ -145,6 +274,7 @@ class ResourceOperations extends Component ])->fill($payload); $newEnvironmentVariable->save(); } + $route = route('project.database.configuration', [ 'project_uuid' => $this->projectUuid, 'environment_uuid' => $this->environmentUuid, @@ -167,17 +297,53 @@ class ResourceOperations extends Component ]); $new_resource->save(); + + $tags = $this->resource->tags; + foreach ($tags as $tag) { + $new_resource->tags()->attach($tag->id); + } + + $scheduledTasks = $this->resource->scheduled_tasks()->get(); + foreach ($scheduledTasks as $task) { + $newTask = $task->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'uuid' => (string) new Cuid2, + 'service_id' => $new_resource->id, + 'team_id' => currentTeam()->id, + ]); + $newTask->save(); + } + + $environmentVariables = $this->resource->environment_variables()->get(); + foreach ($environmentVariables as $environmentVariable) { + $newEnvironmentVariable = $environmentVariable->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'resourceable_id' => $new_resource->id, + 'resourceable_type' => $new_resource->getMorphClass(), + ]); + $newEnvironmentVariable->save(); + } + foreach ($new_resource->applications() as $application) { $application->update([ 'status' => 'exited', ]); } + foreach ($new_resource->databases() as $database) { $database->update([ 'status' => 'exited', ]); } + $new_resource->parse(); + $route = route('project.service.configuration', [ 'project_uuid' => $this->projectUuid, 'environment_uuid' => $this->environmentUuid, From 3723c846249409971f36ab8cee48a4ce1d5d05c6 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 8 Jan 2025 23:13:05 +0100 Subject: [PATCH 11/19] feat: docker volume data cloning - UI implementation - functional implementation for databases - volume gets cloned successfully --- app/Livewire/Project/CloneMe.php | 49 +++++++++++++++++++ .../views/livewire/project/clone-me.blade.php | 36 +++++++++++++- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/app/Livewire/Project/CloneMe.php b/app/Livewire/Project/CloneMe.php index 5c577b24a..bcecbc76f 100644 --- a/app/Livewire/Project/CloneMe.php +++ b/app/Livewire/Project/CloneMe.php @@ -2,6 +2,10 @@ namespace App\Livewire\Project; +use App\Actions\Application\StopApplication; +use App\Actions\Database\StartDatabase; +use App\Actions\Database\StopDatabase; +use App\Jobs\VolumeCloneJob; use App\Models\Environment; use App\Models\Project; use App\Models\Server; @@ -34,6 +38,8 @@ class CloneMe extends Component public string $newName = ''; + public bool $cloneVolumeData = false; + protected $messages = [ 'selectedServer' => 'Please select a server.', 'selectedDestination' => 'Please select a server & destination.', @@ -50,6 +56,12 @@ class CloneMe extends Component $this->newName = str($this->project->name.'-clone-'.(string) new Cuid2)->slug(); } + public function toggleVolumeCloning(bool $value) + { + $this->cloneVolumeData = $value; + $this->dispatch('refresh'); + } + public function render() { return view('livewire.project.clone-me'); @@ -192,6 +204,27 @@ class CloneMe extends Component 'resource_id' => $newApplication->id, ]); $newPersistentVolume->save(); + + if ($this->cloneVolumeData) { + try { + StopApplication::dispatch($application, false, false); + $sourceVolume = $volume->name; + $targetVolume = $newPersistentVolume->name; + $server = $application->destination->server; + + VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $server, $newPersistentVolume); + + queue_application_deployment( + deployment_uuid: (string) new Cuid2, + application: $application, + server: $server, + destination: $application->destination, + no_questions_asked: true + ); + } catch (\Exception $e) { + logger()->error("Failed to copy volume data for {$volume->name}: ".$e->getMessage()); + } + } } $fileStorages = $application->fileStorages()->get(); @@ -276,6 +309,22 @@ class CloneMe extends Component 'resource_id' => $newDatabase->id, ]); $newPersistentVolume->save(); + + if ($this->cloneVolumeData) { + try { + StopDatabase::dispatch($database); + $sourceVolume = $volume->name; + $targetVolume = $newPersistentVolume->name; + $server = $database->destination->server; + + VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $server, $newPersistentVolume); + + StartDatabase::dispatch($database); + } catch (\Exception $e) { + // Log error but continue with cloning + logger()->error("Failed to copy volume data for {$volume->name}: ".$e->getMessage()); + } + } } $fileStorages = $database->fileStorages()->get(); diff --git a/resources/views/livewire/project/clone-me.blade.php b/resources/views/livewire/project/clone-me.blade.php index 1246af050..da7c8aa84 100644 --- a/resources/views/livewire/project/clone-me.blade.php +++ b/resources/views/livewire/project/clone-me.blade.php @@ -9,7 +9,39 @@ Clone to a new Project Clone to a new Environment -

Servers

+ +
+

Clone Volume Data

+
+ Clone your volume data to the new resources volumes. This process requires a brief container downtime to ensure data consistency. +
+
+ @if(!$cloneVolumeData) +
+ +
+ @else +
+ +
+ @endif +
+
+ +

Servers

Choose the server and network to clone the resources to.
@foreach ($servers->sortBy('id') as $server) @@ -29,7 +61,7 @@ @endforeach
-

Resources

+

Resources

These will be cloned to the new project
@foreach ($environment->applications->sortBy('name') as $application) From 5877cead8912e4fd10fdd49557efc298e9973047 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Wed, 8 Jan 2025 23:16:15 +0100 Subject: [PATCH 12/19] feat: move volume data cloning to a Job --- app/Jobs/VolumeCloneJob.php | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 app/Jobs/VolumeCloneJob.php diff --git a/app/Jobs/VolumeCloneJob.php b/app/Jobs/VolumeCloneJob.php new file mode 100644 index 000000000..95ac14197 --- /dev/null +++ b/app/Jobs/VolumeCloneJob.php @@ -0,0 +1,39 @@ +onQueue('high'); + } + + public function handle() + { + try { + instant_remote_process([ + "docker volume create $this->targetVolume", + "docker run --rm -v $this->sourceVolume:/source -v $this->targetVolume:/target alpine sh -c 'cp -a /source/. /target/ && chown -R 1000:1000 /target'", + ], $this->server); + } catch (\Exception $e) { + logger()->error("Failed to copy volume data for {$this->sourceVolume}: ".$e->getMessage()); + throw $e; + } + } +} From e01401a4dda318687980bb26071869797f77183f Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 9 Jan 2025 13:49:44 +0100 Subject: [PATCH 13/19] fix: volume and fileStorages cloning - fix: volume naming - fix: clone file and directory mounts for services - fix: clone volumes for services - fix: clone scheduled tasks for service applications - fix: clone backup schedules for service databases - feat: clone Volume data for services and applications --- app/Livewire/Project/CloneMe.php | 135 +++++++++++++++++++++++++++++-- 1 file changed, 127 insertions(+), 8 deletions(-) diff --git a/app/Livewire/Project/CloneMe.php b/app/Livewire/Project/CloneMe.php index bcecbc76f..e794ff2ea 100644 --- a/app/Livewire/Project/CloneMe.php +++ b/app/Livewire/Project/CloneMe.php @@ -5,6 +5,8 @@ namespace App\Livewire\Project; use App\Actions\Application\StopApplication; use App\Actions\Database\StartDatabase; use App\Actions\Database\StopDatabase; +use App\Actions\Service\StartService; +use App\Actions\Service\StopService; use App\Jobs\VolumeCloneJob; use App\Models\Environment; use App\Models\Project; @@ -59,7 +61,6 @@ class CloneMe extends Component public function toggleVolumeCloning(bool $value) { $this->cloneVolumeData = $value; - $this->dispatch('refresh'); } public function render() @@ -195,12 +196,19 @@ class CloneMe extends Component $persistentVolumes = $application->persistentStorages()->get(); foreach ($persistentVolumes as $volume) { + $newName = ''; + if (str_starts_with($volume->name, $application->uuid)) { + $newName = str($volume->name)->replace($application->uuid, $newApplication->uuid); + } else { + $newName = $newApplication->uuid.'-'.$volume->name; + } + $newPersistentVolume = $volume->replicate([ 'id', 'created_at', 'updated_at', ])->fill([ - 'name' => $newApplication->uuid.'-'.str($volume->name)->afterLast('-'), + 'name' => $newName, 'resource_id' => $newApplication->id, ]); $newPersistentVolume->save(); @@ -222,7 +230,7 @@ class CloneMe extends Component no_questions_asked: true ); } catch (\Exception $e) { - logger()->error("Failed to copy volume data for {$volume->name}: ".$e->getMessage()); + \Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage()); } } } @@ -295,9 +303,11 @@ class CloneMe extends Component } elseif (str_starts_with($originalName, 'dragonfly-data-')) { $newName = 'dragonfly-data-'.$newDatabase->uuid; } else { - $newName = str($originalName) - ->replaceFirst($database->uuid, $newDatabase->uuid) - ->toString(); + if (str_starts_with($volume->name, $database->uuid)) { + $newName = str($volume->name)->replace($database->uuid, $newDatabase->uuid); + } else { + $newName = $newDatabase->uuid.'-'.$volume->name; + } } $newPersistentVolume = $volume->replicate([ @@ -321,8 +331,7 @@ class CloneMe extends Component StartDatabase::dispatch($database); } catch (\Exception $e) { - // Log error but continue with cloning - logger()->error("Failed to copy volume data for {$volume->name}: ".$e->getMessage()); + \Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage()); } } } @@ -418,12 +427,122 @@ class CloneMe extends Component $application->update([ 'status' => 'exited', ]); + + $persistentVolumes = $application->persistentStorages()->get(); + foreach ($persistentVolumes as $volume) { + $newName = ''; + if (str_starts_with($volume->name, $application->uuid)) { + $newName = str($volume->name)->replace($application->uuid, $application->uuid); + } else { + $newName = $application->uuid.'-'.$volume->name; + } + + $newPersistentVolume = $volume->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'name' => $newName, + 'resource_id' => $application->id, + ]); + $newPersistentVolume->save(); + + if ($this->cloneVolumeData) { + try { + StopService::dispatch($application, false, false); + $sourceVolume = $volume->name; + $targetVolume = $newPersistentVolume->name; + $server = $application->service->destination->server; + + VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $server, $newPersistentVolume); + + StartService::dispatch($application); + } catch (\Exception $e) { + \Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage()); + } + } + } + + $fileStorages = $application->fileStorages()->get(); + foreach ($fileStorages as $storage) { + $newStorage = $storage->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'resource_id' => $application->id, + ]); + $newStorage->save(); + } } foreach ($newService->databases() as $database) { $database->update([ 'status' => 'exited', ]); + + $persistentVolumes = $database->persistentStorages()->get(); + foreach ($persistentVolumes as $volume) { + $newName = ''; + if (str_starts_with($volume->name, $database->uuid)) { + $newName = str($volume->name)->replace($database->uuid, $database->uuid); + } else { + $newName = $database->uuid.'-'.$volume->name; + } + + $newPersistentVolume = $volume->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'name' => $newName, + 'resource_id' => $database->id, + ]); + $newPersistentVolume->save(); + + if ($this->cloneVolumeData) { + try { + StopService::dispatch($database->service, false, false); + $sourceVolume = $volume->name; + $targetVolume = $newPersistentVolume->name; + $server = $database->service->destination->server; + + VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $server, $newPersistentVolume); + + StartService::dispatch($database->service); + } catch (\Exception $e) { + \Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage()); + } + } + } + + $fileStorages = $database->fileStorages()->get(); + foreach ($fileStorages as $storage) { + $newStorage = $storage->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'resource_id' => $database->id, + ]); + $newStorage->save(); + } + + $scheduledBackups = $database->scheduledBackups()->get(); + foreach ($scheduledBackups as $backup) { + $uuid = (string) new Cuid2; + $newBackup = $backup->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'uuid' => $uuid, + 'database_id' => $database->id, + 'database_type' => $database->getMorphClass(), + 'team_id' => currentTeam()->id, + ]); + $newBackup->save(); + } } $newService->parse(); From 1c357f987d3cc50678ba7207fd606f24d2d7b62a Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 9 Jan 2025 13:56:00 +0100 Subject: [PATCH 14/19] fix: view text and helpers --- resources/views/livewire/project/clone-me.blade.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/views/livewire/project/clone-me.blade.php b/resources/views/livewire/project/clone-me.blade.php index da7c8aa84..f613ca2c7 100644 --- a/resources/views/livewire/project/clone-me.blade.php +++ b/resources/views/livewire/project/clone-me.blade.php @@ -19,10 +19,10 @@ @if(!$cloneVolumeData)
@@ -30,12 +30,12 @@ @else
+ helper="Volume Data will be cloned to the new resources. Containers will be temporarily stopped during the cloning process." />
@endif
From 34873b2c5972c323dbb504fc194ab728b6fec070 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:13:09 +0100 Subject: [PATCH 15/19] feat: volume cloning for ResourceOperations --- .../Project/Shared/ResourceOperations.php | 140 +++++++++++++++++- .../shared/resource-operations.blade.php | 36 ++++- 2 files changed, 166 insertions(+), 10 deletions(-) diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php index cb9f7db85..705a8b49a 100644 --- a/app/Livewire/Project/Shared/ResourceOperations.php +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -2,6 +2,12 @@ namespace App\Livewire\Project\Shared; +use App\Actions\Application\StopApplication; +use App\Actions\Database\StartDatabase; +use App\Actions\Database\StopDatabase; +use App\Actions\Service\StartService; +use App\Actions\Service\StopService; +use App\Jobs\VolumeCloneJob; use App\Models\Environment; use App\Models\Project; use App\Models\StandaloneDocker; @@ -21,6 +27,8 @@ class ResourceOperations extends Component public $servers; + public bool $cloneVolumeData = false; + public function mount() { $parameters = get_route_parameters(); @@ -30,6 +38,11 @@ class ResourceOperations extends Component $this->servers = currentTeam()->servers; } + public function toggleVolumeCloning(bool $value) + { + $this->cloneVolumeData = $value; + } + public function cloneTo($destination_id) { $new_destination = StandaloneDocker::find($destination_id); @@ -118,19 +131,43 @@ class ResourceOperations extends Component $persistentVolumes = $this->resource->persistentStorages()->get(); foreach ($persistentVolumes as $volume) { - $volumeName = str($volume->name)->replace($this->resource->uuid, $new_resource->uuid)->value(); - if ($volumeName === $volume->name) { - $volumeName = $new_resource->uuid.'-'.str($volume->name)->afterLast('-'); + $newName = ''; + if (str_starts_with($volume->name, $this->resource->uuid)) { + $newName = str($volume->name)->replace($this->resource->uuid, $new_resource->uuid); + } else { + $newName = $new_resource->uuid.'-'.str($volume->name)->afterLast('-'); } + $newPersistentVolume = $volume->replicate([ 'id', 'created_at', 'updated_at', ])->fill([ - 'name' => $volumeName, + 'name' => $newName, 'resource_id' => $new_resource->id, ]); $newPersistentVolume->save(); + + if ($this->cloneVolumeData) { + try { + StopApplication::dispatch($this->resource, false, false); + $sourceVolume = $volume->name; + $targetVolume = $newPersistentVolume->name; + $server = $this->resource->destination->server; + + VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $server, $newPersistentVolume); + + queue_application_deployment( + deployment_uuid: (string) new Cuid2, + application: $this->resource, + server: $server, + destination: $this->resource->destination, + no_questions_asked: true + ); + } catch (\Exception $e) { + \Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage()); + } + } } $fileStorages = $this->resource->fileStorages()->get(); @@ -217,9 +254,11 @@ class ResourceOperations extends Component } elseif (str_starts_with($originalName, 'dragonfly-data-')) { $newName = 'dragonfly-data-'.$new_resource->uuid; } else { - $newName = str($originalName) - ->replaceFirst($this->resource->uuid, $new_resource->uuid) - ->toString(); + if (str_starts_with($volume->name, $this->resource->uuid)) { + $newName = str($volume->name)->replace($this->resource->uuid, $new_resource->uuid); + } else { + $newName = $new_resource->uuid.'-'.$volume->name; + } } $newPersistentVolume = $volume->replicate([ @@ -231,6 +270,21 @@ class ResourceOperations extends Component 'resource_id' => $new_resource->id, ]); $newPersistentVolume->save(); + + if ($this->cloneVolumeData) { + try { + StopDatabase::dispatch($this->resource); + $sourceVolume = $volume->name; + $targetVolume = $newPersistentVolume->name; + $server = $this->resource->destination->server; + + VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $server, $newPersistentVolume); + + StartDatabase::dispatch($this->resource); + } catch (\Exception $e) { + \Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage()); + } + } } $fileStorages = $this->resource->fileStorages()->get(); @@ -293,7 +347,7 @@ class ResourceOperations extends Component 'name' => $this->resource->name.'-clone-'.$uuid, 'destination_id' => $new_destination->id, 'destination_type' => $new_destination->getMorphClass(), - 'server_id' => $new_destination->server_id, // server_id is probably not needed anymore because of the new polymorphic relationships (here it is needed for clone to work - but maybe we can drop the column) + 'server_id' => $new_destination->server_id, // server_id is probably not needed anymore because of the new polymorphic relationships (here it is needed for clone to a different server to work - but maybe we can drop the column) ]); $new_resource->save(); @@ -334,12 +388,82 @@ class ResourceOperations extends Component $application->update([ 'status' => 'exited', ]); + + $persistentVolumes = $application->persistentStorages()->get(); + foreach ($persistentVolumes as $volume) { + $newName = ''; + if (str_starts_with($volume->name, $volume->resource->uuid)) { + $newName = str($volume->name)->replace($volume->resource->uuid, $application->uuid); + } else { + $newName = $application->uuid.'-'.str($volume->name)->afterLast('-'); + } + + $newPersistentVolume = $volume->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'name' => $newName, + 'resource_id' => $application->id, + ]); + $newPersistentVolume->save(); + + if ($this->cloneVolumeData) { + try { + StopService::dispatch($application, false, false); + $sourceVolume = $volume->name; + $targetVolume = $newPersistentVolume->name; + $server = $application->service->destination->server; + + VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $server, $newPersistentVolume); + + StartService::dispatch($application); + } catch (\Exception $e) { + \Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage()); + } + } + } } foreach ($new_resource->databases() as $database) { $database->update([ 'status' => 'exited', ]); + + $persistentVolumes = $database->persistentStorages()->get(); + foreach ($persistentVolumes as $volume) { + $newName = ''; + if (str_starts_with($volume->name, $volume->resource->uuid)) { + $newName = str($volume->name)->replace($volume->resource->uuid, $database->uuid); + } else { + $newName = $database->uuid.'-'.str($volume->name)->afterLast('-'); + } + + $newPersistentVolume = $volume->replicate([ + 'id', + 'created_at', + 'updated_at', + ])->fill([ + 'name' => $newName, + 'resource_id' => $database->id, + ]); + $newPersistentVolume->save(); + + if ($this->cloneVolumeData) { + try { + StopService::dispatch($database->service, false, false); + $sourceVolume = $volume->name; + $targetVolume = $newPersistentVolume->name; + $server = $database->service->destination->server; + + VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $server, $newPersistentVolume); + + StartService::dispatch($database->service); + } catch (\Exception $e) { + \Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage()); + } + } + } } $new_resource->parse(); diff --git a/resources/views/livewire/project/shared/resource-operations.blade.php b/resources/views/livewire/project/shared/resource-operations.blade.php index f70af3339..afb6d2cc1 100644 --- a/resources/views/livewire/project/shared/resource-operations.blade.php +++ b/resources/views/livewire/project/shared/resource-operations.blade.php @@ -1,8 +1,40 @@

Resource Operations

-
You can easily make different kind of operations on this resource.
+
You can easily make different kind of operations on this resource.
+ +
+

Clone Volume Data

+
+ Clone your volume data to the new resources volumes. This process requires a brief container downtime to ensure data consistency. +
+
+ @if(!$cloneVolumeData) +
+ +
+ @else +
+ +
+ @endif +
+
+

Clone

-
To another project / environment on a different server.
+
To another project / environment on a different / same server.
@foreach ($servers->sortBy('id') as $server) From 76f13125fff4dd495727b82240d26d171b7b40c3 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 13 Jan 2025 11:13:15 +0100 Subject: [PATCH 16/19] feat: remote server volume cloning --- app/Jobs/VolumeCloneJob.php | 77 ++++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/app/Jobs/VolumeCloneJob.php b/app/Jobs/VolumeCloneJob.php index 95ac14197..f37a9704e 100644 --- a/app/Jobs/VolumeCloneJob.php +++ b/app/Jobs/VolumeCloneJob.php @@ -15,10 +15,13 @@ class VolumeCloneJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + protected string $cloneDir = '/data/coolify/clone'; + public function __construct( protected string $sourceVolume, protected string $targetVolume, - protected Server $server, + protected Server $sourceServer, + protected ?Server $targetServer, protected LocalPersistentVolume $persistentVolume ) { $this->onQueue('high'); @@ -27,13 +30,75 @@ class VolumeCloneJob implements ShouldBeEncrypted, ShouldQueue public function handle() { try { - instant_remote_process([ - "docker volume create $this->targetVolume", - "docker run --rm -v $this->sourceVolume:/source -v $this->targetVolume:/target alpine sh -c 'cp -a /source/. /target/ && chown -R 1000:1000 /target'", - ], $this->server); + if (! $this->targetServer || $this->targetServer->id === $this->sourceServer->id) { + $this->cloneLocalVolume(); + } else { + $this->cloneRemoteVolume(); + } } catch (\Exception $e) { - logger()->error("Failed to copy volume data for {$this->sourceVolume}: ".$e->getMessage()); + \Log::error("Failed to copy volume data for {$this->sourceVolume}: ".$e->getMessage()); throw $e; } } + + protected function cloneLocalVolume() + { + instant_remote_process([ + "docker volume create $this->targetVolume", + "docker run --rm -v $this->sourceVolume:/source -v $this->targetVolume:/target alpine sh -c 'cp -a /source/. /target/ && chown -R 1000:1000 /target'", + ], $this->sourceServer); + } + + protected function cloneRemoteVolume() + { + $sourceCloneDir = "{$this->cloneDir}/{$this->sourceVolume}"; + $targetCloneDir = "{$this->cloneDir}/{$this->targetVolume}"; + + try { + instant_remote_process([ + "mkdir -p $sourceCloneDir", + "chmod 777 $sourceCloneDir", + "docker run --rm -v $this->sourceVolume:/source -v $sourceCloneDir:/clone alpine sh -c 'cd /source && tar czf /clone/volume-data.tar.gz .'", + ], $this->sourceServer); + + instant_remote_process([ + "mkdir -p $targetCloneDir", + "chmod 777 $targetCloneDir", + ], $this->targetServer); + + instant_scp( + "$sourceCloneDir/volume-data.tar.gz", + "$targetCloneDir/volume-data.tar.gz", + $this->sourceServer, + $this->targetServer + ); + + instant_remote_process([ + "docker volume create $this->targetVolume", + "docker run --rm -v $this->targetVolume:/target -v $targetCloneDir:/clone alpine sh -c 'cd /target && tar xzf /clone/volume-data.tar.gz && chown -R 1000:1000 /target'", + ], $this->targetServer); + + } catch (\Exception $e) { + \Log::error("Failed to clone volume {$this->sourceVolume} to {$this->targetVolume}: ".$e->getMessage()); + throw $e; + } finally { + try { + instant_remote_process([ + "rm -rf $sourceCloneDir", + ], $this->sourceServer, false); + } catch (\Exception $e) { + \Log::warning('Failed to clean up source server clone directory: '.$e->getMessage()); + } + + try { + if ($this->targetServer) { + instant_remote_process([ + "rm -rf $targetCloneDir", + ], $this->targetServer, false); + } + } catch (\Exception $e) { + \Log::warning('Failed to clean up target server clone directory: '.$e->getMessage()); + } + } + } } From 43dacd83c9f1afa9bbddad9211d223b72b3572d6 Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 13 Jan 2025 11:13:35 +0100 Subject: [PATCH 17/19] chore: use the new job dispatch --- app/Livewire/Project/CloneMe.php | 22 +++++++++++-------- .../Project/Shared/ResourceOperations.php | 22 +++++++++++-------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/app/Livewire/Project/CloneMe.php b/app/Livewire/Project/CloneMe.php index e794ff2ea..c71f6db64 100644 --- a/app/Livewire/Project/CloneMe.php +++ b/app/Livewire/Project/CloneMe.php @@ -218,14 +218,15 @@ class CloneMe extends Component StopApplication::dispatch($application, false, false); $sourceVolume = $volume->name; $targetVolume = $newPersistentVolume->name; - $server = $application->destination->server; + $sourceServer = $application->destination->server; + $targetServer = $newApplication->destination->server; - VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $server, $newPersistentVolume); + VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume); queue_application_deployment( deployment_uuid: (string) new Cuid2, application: $application, - server: $server, + server: $sourceServer, destination: $application->destination, no_questions_asked: true ); @@ -325,9 +326,10 @@ class CloneMe extends Component StopDatabase::dispatch($database); $sourceVolume = $volume->name; $targetVolume = $newPersistentVolume->name; - $server = $database->destination->server; + $sourceServer = $database->destination->server; + $targetServer = $newDatabase->destination->server; - VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $server, $newPersistentVolume); + VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume); StartDatabase::dispatch($database); } catch (\Exception $e) { @@ -452,9 +454,10 @@ class CloneMe extends Component StopService::dispatch($application, false, false); $sourceVolume = $volume->name; $targetVolume = $newPersistentVolume->name; - $server = $application->service->destination->server; + $sourceServer = $application->service->destination->server; + $targetServer = $newService->destination->server; - VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $server, $newPersistentVolume); + VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume); StartService::dispatch($application); } catch (\Exception $e) { @@ -505,9 +508,10 @@ class CloneMe extends Component StopService::dispatch($database->service, false, false); $sourceVolume = $volume->name; $targetVolume = $newPersistentVolume->name; - $server = $database->service->destination->server; + $sourceServer = $database->service->destination->server; + $targetServer = $newService->destination->server; - VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $server, $newPersistentVolume); + VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume); StartService::dispatch($database->service); } catch (\Exception $e) { diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php index 705a8b49a..e19f1272d 100644 --- a/app/Livewire/Project/Shared/ResourceOperations.php +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -153,14 +153,15 @@ class ResourceOperations extends Component StopApplication::dispatch($this->resource, false, false); $sourceVolume = $volume->name; $targetVolume = $newPersistentVolume->name; - $server = $this->resource->destination->server; + $sourceServer = $this->resource->destination->server; + $targetServer = $new_resource->destination->server; - VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $server, $newPersistentVolume); + VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume); queue_application_deployment( deployment_uuid: (string) new Cuid2, application: $this->resource, - server: $server, + server: $sourceServer, destination: $this->resource->destination, no_questions_asked: true ); @@ -276,9 +277,10 @@ class ResourceOperations extends Component StopDatabase::dispatch($this->resource); $sourceVolume = $volume->name; $targetVolume = $newPersistentVolume->name; - $server = $this->resource->destination->server; + $sourceServer = $this->resource->destination->server; + $targetServer = $new_resource->destination->server; - VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $server, $newPersistentVolume); + VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume); StartDatabase::dispatch($this->resource); } catch (\Exception $e) { @@ -413,9 +415,10 @@ class ResourceOperations extends Component StopService::dispatch($application, false, false); $sourceVolume = $volume->name; $targetVolume = $newPersistentVolume->name; - $server = $application->service->destination->server; + $sourceServer = $application->service->destination->server; + $targetServer = $new_resource->destination->server; - VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $server, $newPersistentVolume); + VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume); StartService::dispatch($application); } catch (\Exception $e) { @@ -454,9 +457,10 @@ class ResourceOperations extends Component StopService::dispatch($database->service, false, false); $sourceVolume = $volume->name; $targetVolume = $newPersistentVolume->name; - $server = $database->service->destination->server; + $sourceServer = $database->service->destination->server; + $targetServer = $new_resource->destination->server; - VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $server, $newPersistentVolume); + VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume); StartService::dispatch($database->service); } catch (\Exception $e) { From 36b16e9d6247fd3c056a4be9efa5a73a9e933f7d Mon Sep 17 00:00:00 2001 From: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Date: Mon, 13 Jan 2025 11:14:00 +0100 Subject: [PATCH 18/19] chore: disable volume data cloning for now --- resources/views/livewire/project/clone-me.blade.php | 4 ++-- .../livewire/project/shared/resource-operations.blade.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/views/livewire/project/clone-me.blade.php b/resources/views/livewire/project/clone-me.blade.php index f613ca2c7..59ece57d6 100644 --- a/resources/views/livewire/project/clone-me.blade.php +++ b/resources/views/livewire/project/clone-me.blade.php @@ -9,7 +9,7 @@ Clone to a new Project Clone to a new Environment - +{{--

Clone Volume Data

@@ -39,7 +39,7 @@
@endif
-
+
--}}

Servers

Choose the server and network to clone the resources to.
diff --git a/resources/views/livewire/project/shared/resource-operations.blade.php b/resources/views/livewire/project/shared/resource-operations.blade.php index afb6d2cc1..4bbc28c75 100644 --- a/resources/views/livewire/project/shared/resource-operations.blade.php +++ b/resources/views/livewire/project/shared/resource-operations.blade.php @@ -1,7 +1,7 @@

Resource Operations

You can easily make different kind of operations on this resource.
- +{{--

Clone Volume Data

@@ -31,7 +31,7 @@
@endif
-
+
--}}

Clone

To another project / environment on a different / same server.
From aab5398b80148f0e2f62677d6016da1f19736ec9 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Tue, 14 Jan 2025 08:49:03 +0100 Subject: [PATCH 19/19] fix: monaco editor disabled state --- app/Providers/HorizonServiceProvider.php | 15 --------------- .../components/forms/monaco-editor.blade.php | 7 +++++-- .../project/application/general.blade.php | 19 ++++++++++++------- 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/app/Providers/HorizonServiceProvider.php b/app/Providers/HorizonServiceProvider.php index 2e2b79a59..f851f1f3b 100644 --- a/app/Providers/HorizonServiceProvider.php +++ b/app/Providers/HorizonServiceProvider.php @@ -4,30 +4,15 @@ namespace App\Providers; use App\Models\User; use Illuminate\Support\Facades\Gate; -use Laravel\Horizon\Horizon; use Laravel\Horizon\HorizonApplicationServiceProvider; class HorizonServiceProvider extends HorizonApplicationServiceProvider { - /** - * Bootstrap any application services. - */ public function boot(): void { parent::boot(); - - // Horizon::routeSmsNotificationsTo('15556667777'); - // Horizon::routeMailNotificationsTo('example@example.com'); - // Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel'); - - Horizon::night(); } - /** - * Register the Horizon gate. - * - * This gate determines who can access Horizon in non-local environments. - */ protected function gate(): void { Gate::define('viewHorizon', function ($user) { diff --git a/resources/views/components/forms/monaco-editor.blade.php b/resources/views/components/forms/monaco-editor.blade.php index c25080cdd..690e654d4 100644 --- a/resources/views/components/forms/monaco-editor.blade.php +++ b/resources/views/components/forms/monaco-editor.blade.php @@ -54,7 +54,10 @@ fontSize: monacoFontSize, lineNumbersMinChars: 3, automaticLayout: true, - language: '{{ $language }}' + language: '{{ $language }}', + domReadOnly: '{{ $readonly ?? false }}', + contextmenu: '!{{ $readonly ?? false }}', + renderLineHighlight: '{{ $readonly ?? false }} ? none : all' }); const observer = new MutationObserver((mutations) => { @@ -95,7 +98,7 @@ }, 5);" :id="monacoId">
-
+
- + id="application.settings.is_container_label_readonly_enabled" instantSave> --}}
@endif @if ($application->dockerfile) @@ -308,15 +308,20 @@ @endif
- + @if ($application->settings->is_container_label_readonly_enabled) + + @else + + @endif
- +