mirror of
https://github.com/Finsys/dockhand.git
synced 2026-06-17 19:09:33 +03:00
167 lines
5.1 KiB
TypeScript
167 lines
5.1 KiB
TypeScript
import { json } from '@sveltejs/kit';
|
|
import type { RequestHandler } from './$types';
|
|
import { authorize } from '$lib/server/authorize';
|
|
import { listContainers, pullImage, inspectContainer } from '$lib/server/docker';
|
|
import { auditContainer } from '$lib/server/audit';
|
|
import { recreateContainer, updateStackContainer } from '$lib/server/scheduler/tasks/container-update';
|
|
|
|
export interface BatchUpdateResult {
|
|
containerId: string;
|
|
containerName: string;
|
|
success: boolean;
|
|
error?: string;
|
|
}
|
|
|
|
/**
|
|
* Batch update containers by recreating them with latest images.
|
|
* Preserves ALL container settings including health checks, resource limits,
|
|
* capabilities, DNS, security options, ulimits, and network connections.
|
|
* Expects JSON body: { containerIds: string[] }
|
|
*/
|
|
export const POST: RequestHandler = async (event) => {
|
|
const { url, cookies, request } = event;
|
|
const auth = await authorize(cookies);
|
|
|
|
const envId = url.searchParams.get('env');
|
|
const envIdNum = envId ? parseInt(envId) : undefined;
|
|
|
|
// Need create permission to recreate containers
|
|
if (auth.authEnabled && !await auth.can('containers', 'create', envIdNum)) {
|
|
return json({ error: 'Permission denied' }, { status: 403 });
|
|
}
|
|
|
|
try {
|
|
const body = await request.json();
|
|
const { containerIds } = body as { containerIds: string[] };
|
|
|
|
if (!containerIds || !Array.isArray(containerIds) || containerIds.length === 0) {
|
|
return json({ error: 'containerIds array is required' }, { status: 400 });
|
|
}
|
|
|
|
const results: BatchUpdateResult[] = [];
|
|
|
|
// Process containers sequentially to avoid resource conflicts
|
|
for (const containerId of containerIds) {
|
|
try {
|
|
const containers = await listContainers(true, envIdNum);
|
|
const container = containers.find(c => c.id === containerId);
|
|
|
|
if (!container) {
|
|
results.push({
|
|
containerId,
|
|
containerName: 'unknown',
|
|
success: false,
|
|
error: 'Container not found'
|
|
});
|
|
continue;
|
|
}
|
|
|
|
// Get full container config
|
|
const inspectData = await inspectContainer(containerId, envIdNum) as any;
|
|
const config = inspectData.Config;
|
|
const imageName = config.Image;
|
|
const containerName = container.name;
|
|
|
|
// Pull latest image first
|
|
try {
|
|
await pullImage(imageName, undefined, envIdNum);
|
|
} catch (pullError: any) {
|
|
results.push({
|
|
containerId,
|
|
containerName,
|
|
success: false,
|
|
error: `Pull failed: ${pullError.message}`
|
|
});
|
|
continue;
|
|
}
|
|
|
|
// Detect if container is part of a Docker Compose stack
|
|
const containerLabels = config.Labels || {};
|
|
const composeProject = containerLabels['com.docker.compose.project'];
|
|
const composeService = containerLabels['com.docker.compose.service'];
|
|
const isStackContainer = !!composeProject;
|
|
|
|
let updateSuccess = false;
|
|
let newContainerId = containerId;
|
|
|
|
if (isStackContainer) {
|
|
// Stack container: Try docker compose up -d first
|
|
const stackSuccess = await updateStackContainer(composeProject, composeService!, envIdNum);
|
|
|
|
if (stackSuccess) {
|
|
updateSuccess = true;
|
|
// Find the new container ID
|
|
const updatedContainers = await listContainers(true, envIdNum);
|
|
const updatedContainer = updatedContainers.find(c => c.name === containerName);
|
|
if (updatedContainer) {
|
|
newContainerId = updatedContainer.id;
|
|
}
|
|
} else {
|
|
// Fallback: Stack is external, use container recreation
|
|
updateSuccess = await recreateContainer(containerName, envIdNum);
|
|
if (updateSuccess) {
|
|
const updatedContainers = await listContainers(true, envIdNum);
|
|
const updatedContainer = updatedContainers.find(c => c.name === containerName);
|
|
if (updatedContainer) {
|
|
newContainerId = updatedContainer.id;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Standalone container: Use shared recreation with ALL settings
|
|
updateSuccess = await recreateContainer(containerName, envIdNum);
|
|
if (updateSuccess) {
|
|
const updatedContainers = await listContainers(true, envIdNum);
|
|
const updatedContainer = updatedContainers.find(c => c.name === containerName);
|
|
if (updatedContainer) {
|
|
newContainerId = updatedContainer.id;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!updateSuccess) {
|
|
results.push({
|
|
containerId,
|
|
containerName,
|
|
success: false,
|
|
error: 'Container recreation failed'
|
|
});
|
|
continue;
|
|
}
|
|
|
|
// Audit log
|
|
await auditContainer(event, 'update', newContainerId, containerName, envIdNum, { batchUpdate: true });
|
|
|
|
results.push({
|
|
containerId: newContainerId,
|
|
containerName,
|
|
success: true
|
|
});
|
|
} catch (error: any) {
|
|
results.push({
|
|
containerId,
|
|
containerName: 'unknown',
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
const successCount = results.filter(r => r.success).length;
|
|
const failCount = results.filter(r => !r.success).length;
|
|
|
|
return json({
|
|
success: failCount === 0,
|
|
results,
|
|
summary: {
|
|
total: results.length,
|
|
success: successCount,
|
|
failed: failCount
|
|
}
|
|
});
|
|
} catch (error: any) {
|
|
console.error('Error in batch update:', error);
|
|
return json({ error: 'Failed to batch update containers', details: error.message }, { status: 500 });
|
|
}
|
|
};
|