mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-06 13:10:37 -04:00
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com> Co-authored-by: Aditya Tripathi <aditya@climactic.co>
150 lines
4.2 KiB
PHP
150 lines
4.2 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Enums\ImageScanErrorCode;
|
|
use App\Enums\ImageScanStatus;
|
|
use App\Services\ImageScan\ImageRef;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
|
use InvalidArgumentException;
|
|
|
|
/**
|
|
* Scan result of one image for one resource on one server.
|
|
*/
|
|
class ImageScan extends BaseModel
|
|
{
|
|
use HasFactory;
|
|
|
|
protected $fillable = [
|
|
'server_id',
|
|
'image_sbom_id',
|
|
'image_id',
|
|
'repo_digest',
|
|
'resource_type',
|
|
'resource_id',
|
|
'image_ref',
|
|
'deployment_queue_id',
|
|
'status',
|
|
'error_code',
|
|
'error_context',
|
|
'summary',
|
|
'findings',
|
|
'candidates',
|
|
'base_image_ref',
|
|
'builds_image',
|
|
'is_baseline',
|
|
'grype_db_built_at',
|
|
'scanned_at',
|
|
];
|
|
|
|
protected function casts(): array
|
|
{
|
|
return [
|
|
'status' => ImageScanStatus::class,
|
|
'error_code' => ImageScanErrorCode::class,
|
|
'error_context' => 'array',
|
|
'summary' => 'array',
|
|
'findings' => 'array',
|
|
'candidates' => 'array',
|
|
'builds_image' => 'boolean',
|
|
'is_baseline' => 'boolean',
|
|
'grype_db_built_at' => 'datetime',
|
|
'scanned_at' => 'datetime',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Entries of `candidates`. Written as `{status, checked_at, entries}`; older rows hold a bare list of entries.
|
|
*
|
|
* @return list<array<string, mixed>>
|
|
*/
|
|
public function candidateEntries(): array
|
|
{
|
|
$candidates = $this->candidates;
|
|
if (! is_array($candidates)) {
|
|
return [];
|
|
}
|
|
|
|
$entries = array_is_list($candidates) ? $candidates : ($candidates['entries'] ?? []);
|
|
|
|
return is_array($entries) ? array_values(array_filter($entries, 'is_array')) : [];
|
|
}
|
|
|
|
/**
|
|
* Result of the last candidate check: ok, no_better, cannot_compare, auth_required, or failed. Null before the first check.
|
|
*/
|
|
public function candidateStatus(): ?string
|
|
{
|
|
$status = is_array($this->candidates) && ! array_is_list($this->candidates) ? ($this->candidates['status'] ?? null) : null;
|
|
|
|
return is_string($status) ? $status : null;
|
|
}
|
|
|
|
/**
|
|
* The better candidates Coolify can switch to: newer tags of the same line first, then a newer build of the same
|
|
* tag. A major version is never one of them.
|
|
*
|
|
* @return list<array<string, mixed>>
|
|
*/
|
|
public function updateCandidates(): array
|
|
{
|
|
try {
|
|
$current = ImageRef::parse((string) $this->image_ref);
|
|
} catch (InvalidArgumentException) {
|
|
return [];
|
|
}
|
|
if ($current->digest !== null) {
|
|
return [];
|
|
}
|
|
|
|
$currentTag = $current->tag ?? 'latest';
|
|
$line = [];
|
|
$sameTag = [];
|
|
foreach ($this->candidateEntries() as $entry) {
|
|
if (($entry['better'] ?? false) !== true || ! is_string($entry['ref'] ?? null)) {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
$ref = ImageRef::parse($entry['ref']);
|
|
} catch (InvalidArgumentException) {
|
|
continue;
|
|
}
|
|
if ($ref->qualifiedName() !== $current->qualifiedName() || $ref->digest !== null || $ref->tag === null) {
|
|
continue;
|
|
}
|
|
|
|
$kind = $entry['kind'] ?? null;
|
|
if ($kind === 'line' && $ref->tag !== $currentTag) {
|
|
$line[] = $entry;
|
|
} elseif ($kind === 'same_tag' && $ref->tag === $currentTag) {
|
|
$sameTag[] = $entry;
|
|
}
|
|
}
|
|
|
|
return [...$line, ...$sameTag];
|
|
}
|
|
|
|
public function resource(): MorphTo
|
|
{
|
|
return $this->morphTo('resource');
|
|
}
|
|
|
|
public function server(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Server::class);
|
|
}
|
|
|
|
public function sbom(): BelongsTo
|
|
{
|
|
return $this->belongsTo(ImageSbom::class, 'image_sbom_id');
|
|
}
|
|
|
|
public function deploymentQueue(): BelongsTo
|
|
{
|
|
return $this->belongsTo(ApplicationDeploymentQueue::class, 'deployment_queue_id');
|
|
}
|
|
}
|