mirror of
https://github.com/tiennm99/coolify.git
synced 2026-04-17 17:21:04 +00:00
Fix read-only volume detection and add refresh capability (#7588)
This commit is contained in:
@@ -209,6 +209,23 @@ class LocalFileVolume extends BaseModel
|
||||
return $query->get()->where('plain_mount_path', $path);
|
||||
}
|
||||
|
||||
// Check if this volume belongs to a service resource
|
||||
public function isServiceResource(): bool
|
||||
{
|
||||
return in_array($this->resource_type, [
|
||||
'App\Models\ServiceApplication',
|
||||
'App\Models\ServiceDatabase',
|
||||
]);
|
||||
}
|
||||
|
||||
// Determine if this volume should be read-only in the UI
|
||||
// File/directory mounts can be edited even for services
|
||||
public function shouldBeReadOnlyInUI(): bool
|
||||
{
|
||||
// Check for explicit :ro flag in compose (existing logic)
|
||||
return $this->isReadOnlyVolume();
|
||||
}
|
||||
|
||||
// Check if this volume is read-only by parsing the docker-compose content
|
||||
public function isReadOnlyVolume(): bool
|
||||
{
|
||||
@@ -239,22 +256,40 @@ class LocalFileVolume extends BaseModel
|
||||
$volumes = $compose['services'][$serviceName]['volumes'];
|
||||
|
||||
// Check each volume to find a match
|
||||
// Note: We match on mount_path (container path) only, since fs_path gets transformed
|
||||
// from relative (./file) to absolute (/data/coolify/services/uuid/file) during parsing
|
||||
foreach ($volumes as $volume) {
|
||||
// Volume can be string like "host:container:ro" or "host:container"
|
||||
if (is_string($volume)) {
|
||||
$parts = explode(':', $volume);
|
||||
|
||||
// Check if this volume matches our fs_path and mount_path
|
||||
// Check if this volume matches our mount_path
|
||||
if (count($parts) >= 2) {
|
||||
$hostPath = $parts[0];
|
||||
$containerPath = $parts[1];
|
||||
$options = $parts[2] ?? null;
|
||||
|
||||
// Match based on fs_path and mount_path
|
||||
if ($hostPath === $this->fs_path && $containerPath === $this->mount_path) {
|
||||
// Match based on mount_path
|
||||
// Remove leading slash from mount_path if present for comparison
|
||||
$mountPath = str($this->mount_path)->ltrim('/')->toString();
|
||||
$containerPathClean = str($containerPath)->ltrim('/')->toString();
|
||||
|
||||
if ($mountPath === $containerPathClean || $this->mount_path === $containerPath) {
|
||||
return $options === 'ro';
|
||||
}
|
||||
}
|
||||
} elseif (is_array($volume)) {
|
||||
// Long-form syntax: { type: bind, source: ..., target: ..., read_only: true }
|
||||
$containerPath = data_get($volume, 'target');
|
||||
$readOnly = data_get($volume, 'read_only', false);
|
||||
|
||||
// Match based on mount_path
|
||||
// Remove leading slash from mount_path if present for comparison
|
||||
$mountPath = str($this->mount_path)->ltrim('/')->toString();
|
||||
$containerPathClean = str($containerPath)->ltrim('/')->toString();
|
||||
|
||||
if ($mountPath === $containerPathClean || $this->mount_path === $containerPath) {
|
||||
return $readOnly === true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,11 @@ class LocalPersistentVolume extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
public function resource()
|
||||
{
|
||||
return $this->morphTo('resource');
|
||||
}
|
||||
|
||||
public function application()
|
||||
{
|
||||
return $this->morphTo('resource');
|
||||
@@ -50,6 +55,54 @@ class LocalPersistentVolume extends Model
|
||||
);
|
||||
}
|
||||
|
||||
// Check if this volume belongs to a service resource
|
||||
public function isServiceResource(): bool
|
||||
{
|
||||
return in_array($this->resource_type, [
|
||||
'App\Models\ServiceApplication',
|
||||
'App\Models\ServiceDatabase',
|
||||
]);
|
||||
}
|
||||
|
||||
// Check if this volume belongs to a dockercompose application
|
||||
public function isDockerComposeResource(): bool
|
||||
{
|
||||
if ($this->resource_type !== 'App\Models\Application') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only access relationship if already eager loaded to avoid N+1
|
||||
if (! $this->relationLoaded('resource')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$application = $this->resource;
|
||||
if (! $application) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return data_get($application, 'build_pack') === 'dockercompose';
|
||||
}
|
||||
|
||||
// Determine if this volume should be read-only in the UI
|
||||
// Service volumes and dockercompose application volumes are read-only
|
||||
// (users should edit compose file directly)
|
||||
public function shouldBeReadOnlyInUI(): bool
|
||||
{
|
||||
// All service volumes should be read-only in UI
|
||||
if ($this->isServiceResource()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// All dockercompose application volumes should be read-only in UI
|
||||
if ($this->isDockerComposeResource()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for explicit :ro flag in compose (existing logic)
|
||||
return $this->isReadOnlyVolume();
|
||||
}
|
||||
|
||||
// Check if this volume is read-only by parsing the docker-compose content
|
||||
public function isReadOnlyVolume(): bool
|
||||
{
|
||||
@@ -85,6 +138,7 @@ class LocalPersistentVolume extends Model
|
||||
$volumes = $compose['services'][$serviceName]['volumes'];
|
||||
|
||||
// Check each volume to find a match
|
||||
// Note: We match on mount_path (container path) only, since host paths get transformed
|
||||
foreach ($volumes as $volume) {
|
||||
// Volume can be string like "host:container:ro" or "host:container"
|
||||
if (is_string($volume)) {
|
||||
@@ -104,6 +158,19 @@ class LocalPersistentVolume extends Model
|
||||
return $options === 'ro';
|
||||
}
|
||||
}
|
||||
} elseif (is_array($volume)) {
|
||||
// Long-form syntax: { type: bind/volume, source: ..., target: ..., read_only: true }
|
||||
$containerPath = data_get($volume, 'target');
|
||||
$readOnly = data_get($volume, 'read_only', false);
|
||||
|
||||
// Match based on mount_path
|
||||
// Remove leading slash from mount_path if present for comparison
|
||||
$mountPath = str($this->mount_path)->ltrim('/')->toString();
|
||||
$containerPathClean = str($containerPath)->ltrim('/')->toString();
|
||||
|
||||
if ($mountPath === $containerPathClean || $this->mount_path === $containerPath) {
|
||||
return $readOnly === true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user