feat: volume cloning for ResourceOperations

This commit is contained in:
peaklabs-dev
2025-01-09 14:13:09 +01:00
parent 1c357f987d
commit 34873b2c59
2 changed files with 166 additions and 10 deletions

View File

@@ -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();

View File

@@ -1,8 +1,40 @@
<div>
<h2>Resource Operations</h2>
<div class="pb-4">You can easily make different kind of operations on this resource.</div>
<div>You can easily make different kind of operations on this resource.</div>
<div class="mt-8">
<h3 class="text-lg font-bold mb-2">Clone Volume Data</h3>
<div class="text-sm text-gray-600 dark:text-gray-300 mb-4">
Clone your volume data to the new resources volumes. This process requires a brief container downtime to ensure data consistency.
</div>
<div class="pb-4">
@if(!$cloneVolumeData)
<div wire:key="volume-disabled">
<x-modal-confirmation
title="Enable Volume Data Cloning?"
buttonTitle="Enable Volume Cloning"
submitAction="toggleVolumeCloning(true)"
:actions="['This will temporarily stop all the containers to copy the volume data to the new resources to ensure data consistency.', 'The process runs in the background and may take a few minutes.']"
:confirmWithPassword="false"
:confirmWithText="false"
/>
</div>
@else
<div wire:key="volume-enabled" class="max-w-md">
<x-forms.checkbox
label="Clone Volume Data"
id="cloneVolumeData"
wire:model="cloneVolumeData"
wire:change="toggleVolumeCloning(false)"
:checked="$cloneVolumeData"
helper="Volume Data will be cloned to the new resources. Containers will be temporarily stopped during the cloning process." />
</div>
@endif
</div>
</div>
<h3>Clone</h3>
<div class="pb-4">To another project / environment on a different server.</div>
<div class="pb-4">To another project / environment on a different / same server.</div>
<div class="pb-4">
<div class="flex flex-col flex-wrap gap-2">
@foreach ($servers->sortBy('id') as $server)