mirror of
https://github.com/coollabsio/coolify.git
synced 2026-06-14 03:19:51 +00:00
d423223d38
Add configurable health check settings for standalone databases and apply them to generated Docker Compose services. Allow disabling health checks and cover the behavior with feature tests.
374 lines
11 KiB
PHP
374 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Traits\ClearsGlobalSearchCache;
|
|
use App\Traits\HasDatabaseHealthCheck;
|
|
use App\Traits\HasMetrics;
|
|
use App\Traits\HasSafeStringAttribute;
|
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
|
|
class StandaloneKeydb extends BaseModel
|
|
{
|
|
use ClearsGlobalSearchCache, HasDatabaseHealthCheck, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
|
|
|
protected $fillable = [
|
|
'uuid',
|
|
'name',
|
|
'description',
|
|
'keydb_password',
|
|
'keydb_conf',
|
|
'is_log_drain_enabled',
|
|
'is_include_timestamps',
|
|
'status',
|
|
'image',
|
|
'is_public',
|
|
'public_port',
|
|
'ports_mappings',
|
|
'limits_memory',
|
|
'limits_memory_swap',
|
|
'limits_memory_swappiness',
|
|
'limits_memory_reservation',
|
|
'limits_cpus',
|
|
'limits_cpuset',
|
|
'limits_cpu_shares',
|
|
'started_at',
|
|
'restart_count',
|
|
'last_restart_at',
|
|
'last_restart_type',
|
|
'last_online_at',
|
|
'public_port_timeout',
|
|
'enable_ssl',
|
|
'custom_docker_run_options',
|
|
'destination_type',
|
|
'destination_id',
|
|
'environment_id',
|
|
'health_check_enabled',
|
|
'health_check_interval',
|
|
'health_check_timeout',
|
|
'health_check_retries',
|
|
'health_check_start_period',
|
|
];
|
|
|
|
protected $appends = ['internal_db_url', 'external_db_url', 'server_status'];
|
|
|
|
protected $casts = [
|
|
'health_check_enabled' => 'boolean',
|
|
'health_check_interval' => 'integer',
|
|
'health_check_timeout' => 'integer',
|
|
'health_check_retries' => 'integer',
|
|
'health_check_start_period' => 'integer',
|
|
'keydb_password' => 'encrypted',
|
|
'public_port_timeout' => 'integer',
|
|
'restart_count' => 'integer',
|
|
'last_restart_at' => 'datetime',
|
|
'last_restart_type' => 'string',
|
|
];
|
|
|
|
protected static function booted()
|
|
{
|
|
static::created(function ($database) {
|
|
LocalPersistentVolume::create([
|
|
'name' => 'keydb-data-'.$database->uuid,
|
|
'mount_path' => '/data',
|
|
'host_path' => null,
|
|
'resource_id' => $database->id,
|
|
'resource_type' => $database->getMorphClass(),
|
|
]);
|
|
});
|
|
static::forceDeleting(function ($database) {
|
|
$database->persistentStorages()->delete();
|
|
$database->scheduledBackups()->delete();
|
|
$database->environment_variables()->delete();
|
|
$database->tags()->detach();
|
|
});
|
|
static::saving(function ($database) {
|
|
if ($database->isDirty('status')) {
|
|
$database->last_online_at = now();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get query builder for KeyDB databases owned by current team.
|
|
* If you need all databases without further query chaining, use ownedByCurrentTeamCached() instead.
|
|
*/
|
|
public static function ownedByCurrentTeam()
|
|
{
|
|
return StandaloneKeydb::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name');
|
|
}
|
|
|
|
/**
|
|
* Get all KeyDB databases owned by current team (cached for request duration).
|
|
*/
|
|
public static function ownedByCurrentTeamCached()
|
|
{
|
|
return once(function () {
|
|
return StandaloneKeydb::ownedByCurrentTeam()->get();
|
|
});
|
|
}
|
|
|
|
protected function serverStatus(): Attribute
|
|
{
|
|
return Attribute::make(
|
|
get: function () {
|
|
return $this->destination->server->isFunctional();
|
|
}
|
|
);
|
|
}
|
|
|
|
public function isConfigurationChanged(bool $save = false)
|
|
{
|
|
$newConfigHash = $this->image.$this->ports_mappings.$this->keydb_conf;
|
|
$newConfigHash .= $this->health_check_enabled.$this->health_check_interval.$this->health_check_timeout.$this->health_check_retries.$this->health_check_start_period;
|
|
$newConfigHash .= json_encode($this->environment_variables()->get('value')->sort());
|
|
$newConfigHash = md5($newConfigHash);
|
|
$oldConfigHash = data_get($this, 'config_hash');
|
|
if ($oldConfigHash === null) {
|
|
if ($save) {
|
|
$this->config_hash = $newConfigHash;
|
|
$this->save();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
if ($oldConfigHash === $newConfigHash) {
|
|
return false;
|
|
} else {
|
|
if ($save) {
|
|
$this->config_hash = $newConfigHash;
|
|
$this->save();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public function isRunning()
|
|
{
|
|
return (bool) str($this->status)->contains('running');
|
|
}
|
|
|
|
public function isExited()
|
|
{
|
|
return (bool) str($this->status)->startsWith('exited');
|
|
}
|
|
|
|
public function workdir()
|
|
{
|
|
return database_configuration_dir()."/{$this->uuid}";
|
|
}
|
|
|
|
public function deleteConfigurations()
|
|
{
|
|
$server = data_get($this, 'destination.server');
|
|
$workdir = $this->workdir();
|
|
if (str($workdir)->endsWith($this->uuid)) {
|
|
instant_remote_process(['rm -rf '.$this->workdir()], $server, false);
|
|
}
|
|
}
|
|
|
|
public function deleteVolumes()
|
|
{
|
|
$persistentStorages = $this->persistentStorages()->get() ?? collect();
|
|
if ($persistentStorages->count() === 0) {
|
|
return;
|
|
}
|
|
$server = data_get($this, 'destination.server');
|
|
foreach ($persistentStorages as $storage) {
|
|
instant_remote_process(['docker volume rm -f '.escapeshellarg($storage->name)], $server, false);
|
|
}
|
|
}
|
|
|
|
public function realStatus()
|
|
{
|
|
return $this->getRawOriginal('status');
|
|
}
|
|
|
|
public function status(): Attribute
|
|
{
|
|
return Attribute::make(
|
|
set: function ($value) {
|
|
if (str($value)->contains('(')) {
|
|
$status = str($value)->before('(')->trim()->value();
|
|
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
|
} elseif (str($value)->contains(':')) {
|
|
$status = str($value)->before(':')->trim()->value();
|
|
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
|
} else {
|
|
$status = $value;
|
|
$health = 'unhealthy';
|
|
}
|
|
|
|
return "$status:$health";
|
|
},
|
|
get: function ($value) {
|
|
if (str($value)->contains('(')) {
|
|
$status = str($value)->before('(')->trim()->value();
|
|
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
|
} elseif (str($value)->contains(':')) {
|
|
$status = str($value)->before(':')->trim()->value();
|
|
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
|
} else {
|
|
$status = $value;
|
|
$health = 'unhealthy';
|
|
}
|
|
|
|
return "$status:$health";
|
|
},
|
|
);
|
|
}
|
|
|
|
public function tags()
|
|
{
|
|
return $this->morphToMany(Tag::class, 'taggable');
|
|
}
|
|
|
|
public function project()
|
|
{
|
|
return data_get($this, 'environment.project');
|
|
}
|
|
|
|
public function team()
|
|
{
|
|
return data_get($this, 'environment.project.team');
|
|
}
|
|
|
|
public function sslCertificates()
|
|
{
|
|
return $this->morphMany(SslCertificate::class, 'resource');
|
|
}
|
|
|
|
public function link()
|
|
{
|
|
if (data_get($this, 'environment.project.uuid')) {
|
|
return route('project.database.configuration', [
|
|
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
|
'environment_uuid' => data_get($this, 'environment.uuid'),
|
|
'database_uuid' => data_get($this, 'uuid'),
|
|
]);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function isLogDrainEnabled()
|
|
{
|
|
return data_get($this, 'is_log_drain_enabled', false);
|
|
}
|
|
|
|
public function portsMappings(): Attribute
|
|
{
|
|
return Attribute::make(
|
|
set: fn ($value) => $value === '' ? null : $value,
|
|
);
|
|
}
|
|
|
|
public function portsMappingsArray(): Attribute
|
|
{
|
|
return Attribute::make(
|
|
get: fn () => is_null($this->ports_mappings)
|
|
? []
|
|
: explode(',', $this->ports_mappings),
|
|
|
|
);
|
|
}
|
|
|
|
public function databaseType(): Attribute
|
|
{
|
|
return new Attribute(
|
|
get: fn () => $this->type(),
|
|
);
|
|
}
|
|
|
|
public function type(): string
|
|
{
|
|
return 'standalone-keydb';
|
|
}
|
|
|
|
protected function internalDbUrl(): Attribute
|
|
{
|
|
return new Attribute(
|
|
get: function () {
|
|
$scheme = $this->enable_ssl ? 'rediss' : 'redis';
|
|
$port = $this->enable_ssl ? 6380 : 6379;
|
|
$encodedPass = rawurlencode($this->keydb_password);
|
|
$url = "{$scheme}://:{$encodedPass}@{$this->uuid}:{$port}/0";
|
|
|
|
if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') {
|
|
$url .= '?cacert=/etc/ssl/certs/coolify-ca.crt';
|
|
}
|
|
|
|
return $url;
|
|
}
|
|
);
|
|
}
|
|
|
|
protected function externalDbUrl(): Attribute
|
|
{
|
|
return new Attribute(
|
|
get: function () {
|
|
if ($this->is_public && $this->public_port) {
|
|
$serverIp = $this->destination->server->getIp;
|
|
if (empty($serverIp)) {
|
|
return null;
|
|
}
|
|
$scheme = $this->enable_ssl ? 'rediss' : 'redis';
|
|
$encodedPass = rawurlencode($this->keydb_password);
|
|
$url = "{$scheme}://:{$encodedPass}@{$serverIp}:{$this->public_port}/0";
|
|
|
|
if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') {
|
|
$url .= '?cacert=/etc/ssl/certs/coolify-ca.crt';
|
|
}
|
|
|
|
return $url;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
);
|
|
}
|
|
|
|
public function environment()
|
|
{
|
|
return $this->belongsTo(Environment::class);
|
|
}
|
|
|
|
public function fileStorages()
|
|
{
|
|
return $this->morphMany(LocalFileVolume::class, 'resource');
|
|
}
|
|
|
|
public function destination()
|
|
{
|
|
return $this->morphTo();
|
|
}
|
|
|
|
public function runtime_environment_variables()
|
|
{
|
|
return $this->morphMany(EnvironmentVariable::class, 'resourceable');
|
|
}
|
|
|
|
public function persistentStorages()
|
|
{
|
|
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
|
}
|
|
|
|
public function scheduledBackups()
|
|
{
|
|
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
|
}
|
|
|
|
public function isBackupSolutionAvailable()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public function environment_variables()
|
|
{
|
|
return $this->morphMany(EnvironmentVariable::class, 'resourceable');
|
|
}
|
|
}
|