This commit is contained in:
@@ -86,25 +86,26 @@ async function getLatestDigest(image: string, tag: string): Promise<string | nul
|
|||||||
} catch { return null }
|
} catch { return null }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getGhcrLatestDigest(image: string, tag: string): Promise<string | null> {
|
async function getGenericRegistryDigest(
|
||||||
const cacheKey = `ghcr:${image}:${tag}`
|
registry: string, image: string, tag: string, bearerToken?: string
|
||||||
|
): Promise<string | null> {
|
||||||
|
const cacheKey = `${registry}:${image}:${tag}`
|
||||||
const cached = fromCache<string>(cacheKey, HUB_TTL)
|
const cached = fromCache<string>(cacheKey, HUB_TTL)
|
||||||
if (cached) return cached
|
if (cached) return cached
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ghToken = process.env.GITHUB_TOKEN
|
const authHeaders: Record<string, string> = bearerToken
|
||||||
const authHeaders: Record<string, string> = ghToken
|
? { Authorization: `Bearer ${bearerToken}` }
|
||||||
? { Authorization: `Bearer ${ghToken}` }
|
|
||||||
: {}
|
: {}
|
||||||
|
|
||||||
const tokenRes = await axios.get(
|
const tokenRes = await axios.get(
|
||||||
`https://ghcr.io/token?service=ghcr.io&scope=repository:${image}:pull`,
|
`https://${registry}/token?service=${registry}&scope=repository:${image}:pull`,
|
||||||
{ headers: authHeaders, timeout: 8000 }
|
{ headers: authHeaders, timeout: 8000 }
|
||||||
)
|
)
|
||||||
const token = tokenRes.data.token as string
|
const token = tokenRes.data.token as string
|
||||||
|
|
||||||
const res = await axios.head(
|
const res = await axios.head(
|
||||||
`https://ghcr.io/v2/${image}/manifests/${tag}`,
|
`https://${registry}/v2/${image}/manifests/${tag}`,
|
||||||
{ headers: { Authorization: `Bearer ${token}`, Accept: MANIFEST_ACCEPT }, timeout: 10000 }
|
{ headers: { Authorization: `Bearer ${token}`, Accept: MANIFEST_ACCEPT }, timeout: 10000 }
|
||||||
)
|
)
|
||||||
const digest = res.headers['docker-content-digest'] as string | undefined
|
const digest = res.headers['docker-content-digest'] as string | undefined
|
||||||
@@ -113,6 +114,14 @@ async function getGhcrLatestDigest(image: string, tag: string): Promise<string |
|
|||||||
} catch { return null }
|
} catch { return null }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getGhcrLatestDigest(image: string, tag: string): Promise<string | null> {
|
||||||
|
return getGenericRegistryDigest('ghcr.io', image, tag, process.env.GITHUB_TOKEN)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getLscrLatestDigest(image: string, tag: string): Promise<string | null> {
|
||||||
|
return getGenericRegistryDigest('lscr.io', image, tag)
|
||||||
|
}
|
||||||
|
|
||||||
router.get('/docker', async (_req, res) => {
|
router.get('/docker', async (_req, res) => {
|
||||||
const host = process.env.PORTAINER_HOST
|
const host = process.env.PORTAINER_HOST
|
||||||
if (!host) {
|
if (!host) {
|
||||||
@@ -168,6 +177,7 @@ router.get('/docker', async (_req, res) => {
|
|||||||
const { registry, name, tag } = parseImage(rawImage)
|
const { registry, name, tag } = parseImage(rawImage)
|
||||||
const isDockerHub = registry === 'docker.io'
|
const isDockerHub = registry === 'docker.io'
|
||||||
const isGhcr = registry === 'ghcr.io'
|
const isGhcr = registry === 'ghcr.io'
|
||||||
|
const isLscr = registry === 'lscr.io'
|
||||||
|
|
||||||
let upToDate: boolean | null = null
|
let upToDate: boolean | null = null
|
||||||
if (isDockerHub && repoDigest) {
|
if (isDockerHub && repoDigest) {
|
||||||
@@ -176,6 +186,9 @@ router.get('/docker', async (_req, res) => {
|
|||||||
} else if (isGhcr && repoDigest) {
|
} else if (isGhcr && repoDigest) {
|
||||||
const latest = await getGhcrLatestDigest(name, tag)
|
const latest = await getGhcrLatestDigest(name, tag)
|
||||||
if (latest) upToDate = latest === repoDigest
|
if (latest) upToDate = latest === repoDigest
|
||||||
|
} else if (isLscr && repoDigest) {
|
||||||
|
const latest = await getLscrLatestDigest(name, tag)
|
||||||
|
if (latest) upToDate = latest === repoDigest
|
||||||
}
|
}
|
||||||
|
|
||||||
containers.push({
|
containers.push({
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ export function DockerUpdatesWidget() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const outdated = containers.filter(c => c.upToDate === false).length
|
const outdated = containers.filter(c => c.upToDate === false).length
|
||||||
const unknown = containers.filter(c => c.upToDate === null && c.registry !== 'docker.io' && c.registry !== 'ghcr.io').length
|
const knownRegistries = ['docker.io', 'ghcr.io', 'lscr.io']
|
||||||
|
const unknown = containers.filter(c => c.upToDate === null && !knownRegistries.includes(c.registry)).length
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
@@ -58,7 +59,7 @@ export function DockerUpdatesWidget() {
|
|||||||
{c.image}:{c.tag.startsWith('sha256:') ? c.tag.slice(0, 15) + '…' : c.tag} · {c.endpoint}
|
{c.image}:{c.tag.startsWith('sha256:') ? c.tag.slice(0, 15) + '…' : c.tag} · {c.endpoint}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{c.upToDate === null && c.registry !== 'docker.io' && c.registry !== 'ghcr.io' ? (
|
{c.upToDate === null && !knownRegistries.includes(c.registry) ? (
|
||||||
<span className="pill pill-blue">ext</span>
|
<span className="pill pill-blue">ext</span>
|
||||||
) : c.upToDate === true ? (
|
) : c.upToDate === true ? (
|
||||||
<span className="pill pill-green">✓</span>
|
<span className="pill pill-green">✓</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user