mirror of
https://github.com/khoaliber/dockhand.git
synced 2026-03-06 21:29:05 +00:00
Initial commit
This commit is contained in:
109
routes/api/registry/image/+server.ts
Normal file
109
routes/api/registry/image/+server.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getRegistry } from '$lib/server/db';
|
||||
|
||||
function isDockerHub(url: string): boolean {
|
||||
const lower = url.toLowerCase();
|
||||
return lower.includes('docker.io') ||
|
||||
lower.includes('hub.docker.com') ||
|
||||
lower.includes('registry.hub.docker.com');
|
||||
}
|
||||
|
||||
export const DELETE: RequestHandler = async ({ url }) => {
|
||||
try {
|
||||
const registryId = url.searchParams.get('registry');
|
||||
const imageName = url.searchParams.get('image');
|
||||
const tag = url.searchParams.get('tag');
|
||||
|
||||
if (!registryId) {
|
||||
return json({ error: 'Registry ID is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
if (!imageName) {
|
||||
return json({ error: 'Image name is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
if (!tag) {
|
||||
return json({ error: 'Tag is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const registry = await getRegistry(parseInt(registryId));
|
||||
if (!registry) {
|
||||
return json({ error: 'Registry not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Docker Hub doesn't support deletion via API
|
||||
if (isDockerHub(registry.url)) {
|
||||
return json({ error: 'Docker Hub does not support image deletion via API. Please use the Docker Hub web interface.' }, { status: 400 });
|
||||
}
|
||||
|
||||
let baseUrl = registry.url;
|
||||
if (!baseUrl.endsWith('/')) {
|
||||
baseUrl += '/';
|
||||
}
|
||||
|
||||
const headers: HeadersInit = {
|
||||
'Accept': 'application/vnd.docker.distribution.manifest.v2+json'
|
||||
};
|
||||
|
||||
if (registry.username && registry.password) {
|
||||
const credentials = Buffer.from(`${registry.username}:${registry.password}`).toString('base64');
|
||||
headers['Authorization'] = `Basic ${credentials}`;
|
||||
}
|
||||
|
||||
// Step 1: Get the manifest digest
|
||||
const manifestUrl = `${baseUrl}v2/${imageName}/manifests/${tag}`;
|
||||
const headResponse = await fetch(manifestUrl, {
|
||||
method: 'HEAD',
|
||||
headers
|
||||
});
|
||||
|
||||
if (!headResponse.ok) {
|
||||
if (headResponse.status === 401) {
|
||||
return json({ error: 'Authentication failed' }, { status: 401 });
|
||||
}
|
||||
if (headResponse.status === 404) {
|
||||
return json({ error: 'Image or tag not found' }, { status: 404 });
|
||||
}
|
||||
return json({ error: `Failed to get manifest: ${headResponse.status}` }, { status: headResponse.status });
|
||||
}
|
||||
|
||||
const digest = headResponse.headers.get('Docker-Content-Digest');
|
||||
if (!digest) {
|
||||
return json({ error: 'Could not get image digest. Registry may not support deletion.' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Step 2: Delete the manifest by digest
|
||||
const deleteUrl = `${baseUrl}v2/${imageName}/manifests/${digest}`;
|
||||
const deleteResponse = await fetch(deleteUrl, {
|
||||
method: 'DELETE',
|
||||
headers
|
||||
});
|
||||
|
||||
if (!deleteResponse.ok) {
|
||||
if (deleteResponse.status === 401) {
|
||||
return json({ error: 'Authentication failed' }, { status: 401 });
|
||||
}
|
||||
if (deleteResponse.status === 404) {
|
||||
return json({ error: 'Manifest not found' }, { status: 404 });
|
||||
}
|
||||
if (deleteResponse.status === 405) {
|
||||
return json({ error: 'Registry does not allow deletion. Enable REGISTRY_STORAGE_DELETE_ENABLED=true on the registry.' }, { status: 405 });
|
||||
}
|
||||
return json({ error: `Failed to delete image: ${deleteResponse.status}` }, { status: deleteResponse.status });
|
||||
}
|
||||
|
||||
return json({ success: true, message: `Deleted ${imageName}:${tag}` });
|
||||
} catch (error: any) {
|
||||
console.error('Error deleting image:', error);
|
||||
|
||||
if (error.code === 'ECONNREFUSED') {
|
||||
return json({ error: 'Could not connect to registry' }, { status: 503 });
|
||||
}
|
||||
if (error.code === 'ENOTFOUND') {
|
||||
return json({ error: 'Registry host not found' }, { status: 503 });
|
||||
}
|
||||
|
||||
return json({ error: error.message || 'Failed to delete image' }, { status: 500 });
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user