mirror of
https://github.com/Finsys/dockhand.git
synced 2026-06-17 19:09:33 +03:00
132 lines
4.1 KiB
TypeScript
132 lines
4.1 KiB
TypeScript
import { json } from '@sveltejs/kit';
|
|
import type { RequestHandler } from './$types';
|
|
import { authorize } from '$lib/server/authorize';
|
|
import { listContainers, inspectContainer, checkImageUpdateAvailable } from '$lib/server/docker';
|
|
import { clearPendingContainerUpdates, addPendingContainerUpdate } from '$lib/server/db';
|
|
import { isSystemContainer } from '$lib/server/scheduler/tasks/update-utils';
|
|
import { isUpdateDisabledByLabel } from '$lib/server/container-labels';
|
|
import { createJobResponse } from '$lib/server/sse';
|
|
|
|
export interface UpdateCheckResult {
|
|
containerId: string;
|
|
containerName: string;
|
|
imageName: string;
|
|
hasUpdate: boolean;
|
|
currentDigest?: string;
|
|
newDigest?: string;
|
|
error?: string;
|
|
isLocalImage?: boolean;
|
|
systemContainer?: 'dockhand' | 'hawser' | null;
|
|
updateDisabled?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Check all containers for available image updates.
|
|
* Returns progress events during checking, final result when done.
|
|
*/
|
|
export const POST: RequestHandler = async ({ url, cookies, request }) => {
|
|
const auth = await authorize(cookies);
|
|
|
|
const envId = url.searchParams.get('env');
|
|
const envIdNum = envId ? parseInt(envId) : undefined;
|
|
|
|
// Need at least view permission
|
|
if (auth.authEnabled && !await auth.can('containers', 'view', envIdNum)) {
|
|
return json({ error: 'Permission denied' }, { status: 403 });
|
|
}
|
|
|
|
return createJobResponse(async (send) => {
|
|
// Clear existing pending updates for this environment before checking
|
|
if (envIdNum) {
|
|
await clearPendingContainerUpdates(envIdNum);
|
|
}
|
|
|
|
const allContainers = await listContainers(true, envIdNum);
|
|
const containers = allContainers;
|
|
|
|
send('progress', { checked: 0, total: containers.length });
|
|
|
|
// Check container for updates
|
|
let checked = 0;
|
|
const checkContainer = async (container: typeof containers[0]): Promise<UpdateCheckResult> => {
|
|
try {
|
|
const inspectData = await inspectContainer(container.id, envIdNum) as any;
|
|
const imageName = inspectData.Config?.Image;
|
|
const currentImageId = inspectData.Image;
|
|
|
|
if (!imageName) {
|
|
return {
|
|
containerId: container.id,
|
|
containerName: container.name,
|
|
imageName: container.image,
|
|
hasUpdate: false,
|
|
error: 'Could not determine image name',
|
|
systemContainer: isSystemContainer(container.image) || null
|
|
};
|
|
}
|
|
|
|
const result = await checkImageUpdateAvailable(imageName, currentImageId, envIdNum);
|
|
const updateDisabled = isUpdateDisabledByLabel(inspectData.Config?.Labels);
|
|
|
|
return {
|
|
containerId: container.id,
|
|
containerName: container.name,
|
|
imageName,
|
|
hasUpdate: result.hasUpdate,
|
|
currentDigest: result.currentDigest,
|
|
newDigest: result.registryDigest,
|
|
error: result.error,
|
|
isLocalImage: result.isLocalImage,
|
|
systemContainer: isSystemContainer(imageName) || null,
|
|
updateDisabled
|
|
};
|
|
} catch (error: any) {
|
|
return {
|
|
containerId: container.id,
|
|
containerName: container.name,
|
|
imageName: container.image,
|
|
hasUpdate: false,
|
|
error: error.message,
|
|
systemContainer: isSystemContainer(container.image) || null
|
|
};
|
|
}
|
|
};
|
|
|
|
// Sliding window concurrency limit to avoid DNS threadpool saturation (#676).
|
|
const CONCURRENCY = 20;
|
|
const results: UpdateCheckResult[] = new Array(containers.length);
|
|
let next = 0;
|
|
async function runNext(): Promise<void> {
|
|
while (next < containers.length) {
|
|
const idx = next++;
|
|
results[idx] = await checkContainer(containers[idx]);
|
|
checked++;
|
|
send('progress', { checked, total: containers.length });
|
|
}
|
|
}
|
|
await Promise.all(Array.from({ length: Math.min(CONCURRENCY, containers.length) }, () => runNext()));
|
|
|
|
const updatesFound = results.filter(r => r.hasUpdate && !r.systemContainer && !r.updateDisabled).length;
|
|
|
|
// Save containers with updates to the database for persistence
|
|
if (envIdNum) {
|
|
for (const result of results) {
|
|
if (result.hasUpdate && !result.systemContainer && !result.updateDisabled) {
|
|
await addPendingContainerUpdate(
|
|
envIdNum,
|
|
result.containerId,
|
|
result.containerName,
|
|
result.imageName
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
send('result', {
|
|
total: containers.length,
|
|
updatesFound,
|
|
results
|
|
});
|
|
}, request);
|
|
};
|