1import fetch from 'node-fetch' 4const regFetchTimeout = 30_000 6const manifestAccept = 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json' 8export const regApiFetch = async ({host, path, auth, method = 'GET', headers: extraHeaders = {}}: { 9 host: string, path: string, auth?: string, method?: string, headers?: Record<string, string> 11 const headers = {...(auth ? {Authorization: `Basic ${auth}`} : {}), ...extraHeaders} 12 return fetch(`https://${host}${path}`, {method, headers, signal: AbortSignal.timeout(regFetchTimeout)}) 15export const resolveRegApiHost = async ({host, lanHost, auth}: {host: string, lanHost?: string, auth?: string}): Promise<string> => { 16 const tryHost = async (tryH: string) => { 18 const resp = await regApiFetch({host: tryH, path: '/v2/', auth, headers: {Accept: '*/*'}}) 19 return resp.status === 200 || resp.status === 401 20 } catch { return false } 23 if (await tryHost(lanHost)) { console.log(`using LAN host ${lanHost}`); return lanHost } 24 console.log(`LAN host ${lanHost} unreachable, falling back to ${host}`) 29export const getRegCatalog = async ({apiHost, auth}: {apiHost: string, auth?: string}): Promise<string[]> => { 30 const resp = await regApiFetch({host: apiHost, path: '/v2/_catalog', auth}) 31 if (resp.status !== 200) throw new Error(`catalog failed: ${resp.status}`) 32 const {repositories} = await resp.json() as {repositories: string[]} 33 return repositories || [] 36export const getRegTags = async ({apiHost, repo, auth}: {apiHost: string, repo: string, auth?: string}): Promise<string[]> => { 37 const resp = await regApiFetch({host: apiHost, path: `/v2/${repo}/tags/list`, auth}) 38 if (resp.status === 404) return [] 39 if (resp.status !== 200) throw new Error(`tags list failed for ${repo}: ${resp.status}`) 40 const {tags} = await resp.json() as {tags: string[]} 44export const getRegDigest = async ({apiHost, repo, tag, auth}: {apiHost: string, repo: string, tag: string, auth?: string}): Promise<string | null> => { 45 const resp = await withTransientDnsRetry(() => regApiFetch({host: apiHost, path: `/v2/${repo}/manifests/${tag}`, auth, method: 'HEAD', headers: {Accept: manifestAccept}})) 46 if (resp.status !== 200) return null 47 return resp.headers.get('Docker-Content-Digest') || null 50export const deleteRegManifest = async ({apiHost, repo, digest, auth}: {apiHost: string, repo: string, digest: string, auth?: string}): Promise<boolean> => { 51 const resp = await regApiFetch({host: apiHost, path: `/v2/${repo}/manifests/${digest}`, auth, method: 'DELETE', headers: {Accept: manifestAccept}}) 52 return resp.status === 202