🌳
pt0/deployF/dockerF/regApiCoreF.mts
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>
10}) => {
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) => {
17 try {
18 const resp = await regApiFetch({host: tryH, path: '/v2/', auth, headers: {Accept: '*/*'}})
19 return resp.status === 200 || resp.status === 401
20 } catch { return false }
21 }
22 if (lanHost) {
23 if (await tryHost(lanHost)) { console.log(`using LAN host ${lanHost}`); return lanHost }
24 console.log(`LAN host ${lanHost} unreachable, falling back to ${host}`)
25 }
26 return 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[]}
41 return tags || []
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