Add Harbor fallback for catalog and search endpoints
Harbor denies access to the V2 _catalog endpoint for robot accounts
(scope registry:catalog:* returns an empty JWT). This adds automatic
Harbor detection and falls back to the native Harbor project API
(/api/v2.0/projects/{name}/repositories) for both catalog listing
and image search.
- Detect Harbor via WWW-Authenticate header + /api/v2.0/ping (cached 5min)
- List repositories through Harbor project API with pagination
- Search repositories using Harbor's q=name=~ filter
- Transparent fallback: no configuration change required
Fixes #360
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
83c3a5ea09
commit
eb5cf32d68
3 files changed
+246
-2
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getRegistry } from '$lib/server/db';
|
||||
import { getRegistryAuth } from '$lib/server/docker';
|
||||
import { getRegistryAuth, isHarborRegistry, harborListRepositories, parseRegistryUrl } from '$lib/server/docker';
|
||||
|
||||
const PAGE_SIZE = 100;
|
||||
|
||||
@@ -24,6 +24,12 @@ export const GET: RequestHandler = async ({ url }) => {
|
||||
return json({ error: 'Docker Hub does not support catalog listing. Please use search instead.' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Fallback Harbor : l'endpoint _catalog est interdit pour les robots Harbor.
|
||||
// On utilise l'API projet native à la place.
|
||||
if (await isHarborRegistry(registry.url)) {
|
||||
return handleHarborCatalog(registry, lastParam);
|
||||
}
|
||||
|
||||
const { baseUrl, orgPath, authHeader } = await getRegistryAuth(registry, 'registry:catalog:*');
|
||||
|
||||
// Build catalog URL with pagination
|
||||
@@ -114,3 +120,39 @@ export const GET: RequestHandler = async ({ url }) => {
|
||||
return json({ error: 'Failed to fetch catalog: ' + (error.message || 'Unknown error') }, { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gère le catalog pour un registry Harbor via l'API projet native.
|
||||
* Décode le curseur "harbor:N" pour la pagination.
|
||||
*/
|
||||
async function handleHarborCatalog(
|
||||
registry: { url: string; username?: string | null; password?: string | null },
|
||||
lastParam: string | null
|
||||
): Promise<Response> {
|
||||
const { path: orgPath } = parseRegistryUrl(registry.url);
|
||||
|
||||
// Décoder le curseur Harbor : "harbor:<page>" → numéro de page
|
||||
let page = 1;
|
||||
if (lastParam?.startsWith('harbor:')) {
|
||||
page = parseInt(lastParam.substring(7), 10) || 1;
|
||||
}
|
||||
|
||||
const result = await harborListRepositories(registry, orgPath, page, PAGE_SIZE);
|
||||
|
||||
const results = result.repositories.map((name: string) => ({
|
||||
name,
|
||||
description: '',
|
||||
star_count: 0,
|
||||
is_official: false,
|
||||
is_automated: false
|
||||
}));
|
||||
|
||||
return json({
|
||||
repositories: results,
|
||||
pagination: {
|
||||
pageSize: PAGE_SIZE,
|
||||
hasMore: !!result.nextLast,
|
||||
nextLast: result.nextLast
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getRegistry } from '$lib/server/db';
|
||||
import { getRegistryAuth, parseRegistryUrl } from '$lib/server/docker';
|
||||
import { getRegistryAuth, isHarborRegistry, harborSearchRepositories, parseRegistryUrl } from '$lib/server/docker';
|
||||
|
||||
interface SearchResult {
|
||||
name: string;
|
||||
@@ -127,6 +127,12 @@ async function tryDirectImageLookup(registry: any, imageName: string): Promise<b
|
||||
|
||||
// Search through catalog (slow for large registries, limited to first few pages)
|
||||
async function searchCatalog(registry: any, term: string, limit: number): Promise<string[]> {
|
||||
// Fallback Harbor : utiliser l'API projet native pour la recherche
|
||||
if (await isHarborRegistry(registry.url)) {
|
||||
const { path: orgPath } = parseRegistryUrl(registry.url);
|
||||
return harborSearchRepositories(registry, term, orgPath, limit);
|
||||
}
|
||||
|
||||
// Note: orgPath could be used here to filter results, but search is already term-based
|
||||
const { baseUrl, authHeader } = await getRegistryAuth(registry, 'registry:catalog:*');
|
||||
|
||||
|
||||
Reference in new issue
Block a user