mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-04 10:42:23 +00:00
173 lines
6.2 KiB
PHP
173 lines
6.2 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Notifications\ScheduledTask\TaskFailed;
|
|
use App\Traits\HasSafeStringAttribute;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
|
use OpenApi\Attributes as OA;
|
|
|
|
#[OA\Schema(
|
|
description: 'Scheduled Task model',
|
|
type: 'object',
|
|
properties: [
|
|
'id' => ['type' => 'integer', 'description' => 'The unique identifier of the scheduled task in the database.'],
|
|
'uuid' => ['type' => 'string', 'description' => 'The unique identifier of the scheduled task.'],
|
|
'enabled' => ['type' => 'boolean', 'description' => 'The flag to indicate if the scheduled task is enabled.'],
|
|
'name' => ['type' => 'string', 'description' => 'The name of the scheduled task.'],
|
|
'command' => ['type' => 'string', 'description' => 'The command to execute.'],
|
|
'frequency' => ['type' => 'string', 'description' => 'The frequency of the scheduled task.'],
|
|
'container' => ['type' => 'string', 'nullable' => true, 'description' => 'The container where the command should be executed.'],
|
|
'timeout' => ['type' => 'integer', 'description' => 'The timeout of the scheduled task in seconds.'],
|
|
'consecutive_failures' => ['type' => 'integer', 'description' => 'The number of failed runs since the last successful run.'],
|
|
'failing_since' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time of the first failed run since the last successful run.'],
|
|
'disabled_reason' => ['type' => 'string', 'nullable' => true, 'description' => 'Why Coolify disabled the task automatically. It is cleared when the task is enabled again.'],
|
|
'created_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the scheduled task was created.'],
|
|
'updated_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the scheduled task was last updated.'],
|
|
],
|
|
)]
|
|
class ScheduledTask extends BaseModel
|
|
{
|
|
use HasFactory;
|
|
use HasSafeStringAttribute;
|
|
|
|
protected $fillable = [
|
|
'uuid',
|
|
'enabled',
|
|
'name',
|
|
'command',
|
|
'frequency',
|
|
'container',
|
|
'timeout',
|
|
'team_id',
|
|
'application_id',
|
|
'service_id',
|
|
];
|
|
|
|
/**
|
|
* A task is disabled automatically only when it failed this many times in a row...
|
|
*/
|
|
public const AUTO_DISABLE_AFTER_FAILURES = 10;
|
|
|
|
/**
|
|
* ...and its first failure is this old, so a short outage does not disable a task that runs often.
|
|
*/
|
|
public const AUTO_DISABLE_AFTER_HOURS = 24;
|
|
|
|
protected static function booted(): void
|
|
{
|
|
// A task that is enabled again starts a new failure streak, and its next failure notifies at once.
|
|
static::saving(function (ScheduledTask $task) {
|
|
if ($task->isDirty('enabled') && $task->enabled) {
|
|
$task->consecutive_failures = 0;
|
|
$task->failing_since = null;
|
|
$task->disabled_reason = null;
|
|
}
|
|
});
|
|
static::saved(function (ScheduledTask $task) {
|
|
if ($task->wasChanged('enabled') && $task->enabled) {
|
|
NotificationThrottle::release($task, TaskFailed::class);
|
|
}
|
|
});
|
|
}
|
|
|
|
public static function ownedByCurrentTeamAPI(int $teamId)
|
|
{
|
|
return static::where('team_id', $teamId)->orderBy('created_at', 'desc');
|
|
}
|
|
|
|
protected function casts(): array
|
|
{
|
|
return [
|
|
'enabled' => 'boolean',
|
|
'timeout' => 'integer',
|
|
'consecutive_failures' => 'integer',
|
|
'failing_since' => 'datetime',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Count a failed run. Disables the task when it failed too often for too long.
|
|
*
|
|
* @return bool True when this failure disabled the task.
|
|
*/
|
|
public function recordFailure(): bool
|
|
{
|
|
$this->refresh();
|
|
$this->consecutive_failures++;
|
|
$this->failing_since ??= now();
|
|
|
|
$disable = $this->enabled
|
|
&& $this->consecutive_failures >= self::AUTO_DISABLE_AFTER_FAILURES
|
|
&& $this->failing_since->lte(now()->subHours(self::AUTO_DISABLE_AFTER_HOURS));
|
|
if ($disable) {
|
|
$this->enabled = false;
|
|
$this->disabled_reason = "Disabled automatically after {$this->consecutive_failures} failed runs in a row since {$this->failing_since->toDateTimeString()} UTC.";
|
|
}
|
|
$this->save();
|
|
|
|
return $disable;
|
|
}
|
|
|
|
/**
|
|
* End the failure streak after a successful run.
|
|
*
|
|
* @return bool True when the team was notified about the failure, so it must be told that the task works again.
|
|
*/
|
|
public function recordSuccess(): bool
|
|
{
|
|
if ($this->consecutive_failures > 0 || $this->failing_since !== null) {
|
|
$this->forceFill(['consecutive_failures' => 0, 'failing_since' => null])->save();
|
|
}
|
|
|
|
return NotificationThrottle::release($this, TaskFailed::class);
|
|
}
|
|
|
|
public function url(): ?string
|
|
{
|
|
$resource = $this->application ?? $this->service;
|
|
if (! $resource) {
|
|
return null;
|
|
}
|
|
$type = $resource instanceof Application ? 'application' : 'service';
|
|
|
|
return base_url().'/project/'.data_get($resource, 'environment.project.uuid').'/environment/'.data_get($resource, 'environment.uuid')."/{$type}/{$resource->uuid}/tasks/{$this->uuid}";
|
|
}
|
|
|
|
public function service()
|
|
{
|
|
return $this->belongsTo(Service::class);
|
|
}
|
|
|
|
public function application()
|
|
{
|
|
return $this->belongsTo(Application::class);
|
|
}
|
|
|
|
public function latest_log(): HasOne
|
|
{
|
|
return $this->hasOne(ScheduledTaskExecution::class)->latest();
|
|
}
|
|
|
|
public function executions(): HasMany
|
|
{
|
|
// Last execution first
|
|
return $this->hasMany(ScheduledTaskExecution::class)->orderBy('created_at', 'desc');
|
|
}
|
|
|
|
public function server(): ?Server
|
|
{
|
|
if ($this->application) {
|
|
return $this->application->destination?->server;
|
|
}
|
|
|
|
if ($this->service) {
|
|
return $this->service->destination?->server;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|