mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
fix(clone): add Compose labels to cloned volumes
Closes #12429 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f2f89ccfae
commit
31653c27d1
@@ -4,6 +4,8 @@ namespace App\Jobs;
|
||||
|
||||
use App\Models\LocalPersistentVolume;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Traits\StagesCloneArchives;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
@@ -202,11 +204,40 @@ class VolumeCloneJob implements ShouldBeEncrypted, ShouldQueue
|
||||
foreach ($configuration['options'] as $key => $value) {
|
||||
$command .= ' --opt '.escapeshellarg("{$key}={$value}");
|
||||
}
|
||||
foreach ($this->composeVolumeLabels() as $key => $value) {
|
||||
$command .= ' --label '.escapeshellarg("{$key}={$value}");
|
||||
}
|
||||
$command .= ' '.escapeshellarg($this->targetVolume);
|
||||
|
||||
return 'sh -c '.escapeshellarg($command);
|
||||
}
|
||||
|
||||
/**
|
||||
* The labels that Docker Compose puts on a volume it creates. Without the project label, every
|
||||
* `docker compose up` of the resource warns that the volume "was not created by Docker Compose".
|
||||
* The project is the `--project-name` of the resource: the service UUID for a service, else the
|
||||
* resource UUID (a standalone database uses its configuration directory, which is its UUID).
|
||||
* The volume key in the generated Compose file is the volume name.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function composeVolumeLabels(): array
|
||||
{
|
||||
$resource = $this->persistentVolume->resource;
|
||||
$project = $resource instanceof ServiceApplication || $resource instanceof ServiceDatabase
|
||||
? $resource->service?->uuid
|
||||
: $resource?->uuid;
|
||||
|
||||
if (blank($project)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'com.docker.compose.project' => (string) $project,
|
||||
'com.docker.compose.volume' => $this->targetVolume,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* A `local` volume with `type: none` and `o: bind` is a host folder. Copying the data would write
|
||||
* into that folder (on the same server, the folder of the source volume itself), so the clone gets
|
||||
|
||||
@@ -6,6 +6,8 @@ use App\Models\InstanceSettings;
|
||||
use App\Models\LocalPersistentVolume;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
@@ -192,3 +194,32 @@ test('a non-root server runs the volume create as one sudo shell script', functi
|
||||
expect(volumeCloneCommands())->toContain('sudo sh -c '.escapeshellarg($inner))
|
||||
->and(volumeCloneCommands())->toContain('sudo docker volume inspect');
|
||||
});
|
||||
|
||||
test('a service clone creates the target volume with the Compose labels of the service project', function () {
|
||||
$service = Service::factory()->create();
|
||||
$serviceApplication = ServiceApplication::create(['name' => 'gitea', 'service_id' => $service->id]);
|
||||
$this->volume->update(['resource_type' => ServiceApplication::class, 'resource_id' => $serviceApplication->id]);
|
||||
fakeVolumeCloneServer(['Name' => 'clone-source', 'Driver' => 'local', 'Options' => null]);
|
||||
|
||||
(new VolumeCloneJob('clone-source', 'clone-target', $this->server, $this->server, $this->volume->fresh()))->handle();
|
||||
|
||||
expect(volumeCloneCommands())->toContain(escapeshellarg(
|
||||
"docker volume create --label 'com.docker.compose.project={$service->uuid}' --label 'com.docker.compose.volume=clone-target' 'clone-target'"
|
||||
));
|
||||
});
|
||||
|
||||
test('an application clone creates the target volume with the Compose labels of the application project', function () {
|
||||
$application = Application::factory()->create();
|
||||
$this->volume->update(['resource_type' => Application::class, 'resource_id' => $application->id]);
|
||||
fakeVolumeCloneServer([
|
||||
'Name' => 'clone-source',
|
||||
'Driver' => 'local',
|
||||
'Options' => ['type' => 'none', 'o' => 'bind', 'device' => '/srv/app-data'],
|
||||
]);
|
||||
|
||||
(new VolumeCloneJob('clone-source', 'clone-target', $this->server, $this->server, $this->volume->fresh()))->handle();
|
||||
|
||||
expect(volumeCloneCommands())->toContain(escapeshellarg(
|
||||
"docker volume create --label 'com.docker.compose.project={$application->uuid}' --label 'com.docker.compose.volume=clone-target' 'clone-target'"
|
||||
));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user