Files
dockhand/lib/server/scheduler/tasks/update-utils.ts
Jarek Krochmalski 62e3c6439e Initial commit
2025-12-28 21:16:03 +01:00

115 lines
3.7 KiB
TypeScript

/**
* Shared utilities for container and environment auto-update tasks.
*/
import type { VulnerabilityCriteria } from '../../db';
import type { VulnerabilitySeverity } from '../../scanner';
/**
* Parse image name and tag from a full image reference.
* Handles various formats:
* - nginx → ["nginx", "latest"]
* - nginx:1.25 → ["nginx", "1.25"]
* - registry.example.com:5000/myimage:v1 → ["registry.example.com:5000/myimage", "v1"]
* - nginx:latest-dockhand-pending → ["nginx", "latest-dockhand-pending"]
*/
export function parseImageNameAndTag(imageName: string): [string, string] {
// Handle digest-based images (return as-is with empty tag)
if (imageName.includes('@sha256:')) {
return [imageName, ''];
}
// Find the last colon that's part of the tag (not part of registry port)
const lastColon = imageName.lastIndexOf(':');
if (lastColon === -1) {
return [imageName, 'latest'];
}
// Check if this colon is part of a registry port
// Registry ports appear before a slash: registry:5000/image
const afterColon = imageName.substring(lastColon + 1);
if (afterColon.includes('/')) {
// The colon is part of the registry, not the tag
return [imageName, 'latest'];
}
// The colon separates repo from tag
return [imageName.substring(0, lastColon), afterColon];
}
/**
* Determine if an update should be blocked based on vulnerability criteria.
*/
export function shouldBlockUpdate(
criteria: VulnerabilityCriteria,
newScanSummary: VulnerabilitySeverity,
currentScanSummary?: VulnerabilitySeverity
): { blocked: boolean; reason: string } {
const totalVulns = newScanSummary.critical + newScanSummary.high + newScanSummary.medium + newScanSummary.low;
switch (criteria) {
case 'any':
if (totalVulns > 0) {
return {
blocked: true,
reason: `Found ${totalVulns} vulnerabilities (${newScanSummary.critical} critical, ${newScanSummary.high} high, ${newScanSummary.medium} medium, ${newScanSummary.low} low)`
};
}
break;
case 'critical_high':
if (newScanSummary.critical > 0 || newScanSummary.high > 0) {
return {
blocked: true,
reason: `Found ${newScanSummary.critical} critical and ${newScanSummary.high} high severity vulnerabilities`
};
}
break;
case 'critical':
if (newScanSummary.critical > 0) {
return {
blocked: true,
reason: `Found ${newScanSummary.critical} critical vulnerabilities`
};
}
break;
case 'more_than_current':
if (currentScanSummary) {
const currentTotal = currentScanSummary.critical + currentScanSummary.high + currentScanSummary.medium + currentScanSummary.low;
if (totalVulns > currentTotal) {
return {
blocked: true,
reason: `New image has ${totalVulns} vulnerabilities vs ${currentTotal} in current image`
};
}
}
break;
case 'never':
default:
break;
}
return { blocked: false, reason: '' };
}
/**
* Check if a container is the Dockhand application itself.
* Used to prevent Dockhand from updating its own container.
*/
export function isDockhandContainer(imageName: string): boolean {
return imageName.toLowerCase().includes('fnsys/dockhand');
}
/**
* Combine multiple scan summaries by taking the maximum of each severity level.
*/
export function combineScanSummaries(results: { summary: VulnerabilitySeverity }[]): VulnerabilitySeverity {
return results.reduce((acc, result) => ({
critical: Math.max(acc.critical, result.summary.critical),
high: Math.max(acc.high, result.summary.high),
medium: Math.max(acc.medium, result.summary.medium),
low: Math.max(acc.low, result.summary.low),
negligible: Math.max(acc.negligible, result.summary.negligible),
unknown: Math.max(acc.unknown, result.summary.unknown)
}), { critical: 0, high: 0, medium: 0, low: 0, negligible: 0, unknown: 0 });
}