Created
July 2, 2022 19:19
-
-
Save achetronic/2db363e6c2fbecd42ae67512fbea50ca to your computer and use it in GitHub Desktop.
Find the tag of a Docker image having only the SHA256
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| SHA256_HASH="5bb4faffc8b35e2702b2ffa78e982b979d7b66db29bd55b0c58de8fa745df661" | |
| for i in {1..1000} | |
| do | |
| echo "Looking into page: $i" | |
| curl "https://registry.hub.docker.com/v2/repositories/apache/superset/tags/?page=$i" \ | |
| | jq '.results[] | select(.["images"][]["digest"] == "sha256:'${SHA256_HASH}'")' | |
| done |
Continuing the trend, this scripts automates both getting the hash and getting tags with the hash (no jq needed).
usage:
# if you have the digest:
docker_tag_pinner 'FROM electronuserland/builder:wine@sha256:8bb6fa0f99a00a5b845521910508958ebbb682b59221f0aa4b82102c22174164'
# if you don't:
docker_tag_pinner 'FROM electronuserland/builder:wine'
Get deno
curl -fsSL https://deno.land/install.sh | sh
Script
#!/usr/bin/env -S deno run --allow-all
import $ from "https://esm.sh/@jsr/[email protected]/mod.ts"
//
// check arg
//
let fromStatment = Deno.args[0].trim()
// ex: fromStatment="docker.io/electronuserland/builder:wine@sha256:8bb6fa0f99a00a5b845521910508958ebbb682b59221f0aa4b82102c22174164"
//
// parse the FROM statement
//
// remove prefixy stuff
fromStatment = fromStatment.replace(/^FROM\s+/, "")
fromStatment = fromStatment.replace(/^docker\.io\//, "")
var [namespace, repo, tag] = fromStatment.split(/\/|:/g)
if (!tag) {
tag = "latest"
}
if (!namespace || !repo) {
console.error("Invalid FROM statement:", fromStatment)
console.error("I'd usually expect an argument like:\n FROM docker.io/electronuserland/builder:wine")
console.error("OR:\n FROM docker.io/electronuserland/builder:wine@sha256:8bb6fa0f99a00a5b845521910508958ebbb682b59221f0aa4b82102c22174164")
Deno.exit(1)
}
if (tag.includes("@")) {
var [tag, hashVersion] = tag.split("@")
}
//
// if digest missing get with `docker images --digests`
//
try_again: while (true) {
if (!hashVersion) {
console.log(`trying to get the hash by running: docker images --digests --format '{{json .}}'`)
const imagesText = await $`docker images --digests --format '{{json .}}'`.text()
const rows = imagesText.split("\n").map(JSON.parse)
const relevantRows = rows.filter((each) => each.Repository.includes(`${namespace}/${repo}`) && each.Tag === tag)
if (relevantRows.length === 0) {
// need to pull it
if (confirm("No digest found for this image. Can I pull it to get the digest?")) {
await $`docker pull ${namespace}/${repo}`
continue try_again
} else {
console.error("No digest found for this image. Please provide the digest manually. (add @DIGEST_HASH to the end of the from statement)")
Deno.exit(1)
}
}
hashVersion = relevantRows[0].Digest
console.log(`digest is: ${hashVersion}`)
}
break
}
hashVersion = hashVersion.replace(/^sha256:/, "")
//
// fetch the tag pages
//
let i = 0
while (true) {
i++
if (i % 100 === 0) {
console.log(`\x1b[35mSleeping for 7 seconds on page ${i}...\x1b[0m`)
await new Promise((resolve) => setTimeout(resolve, 7000))
}
console.log(`\x1b[35mLooking into page: ${i}\x1b[0m`)
const url = `https://registry.hub.docker.com/v2/repositories/${namespace}/${repo}/tags/?page=${i}`
const res = await fetch(url)
if (!res.ok) {
console.error(`Error fetching page ${i}: ${res.statusText}`)
break
}
const data = await res.json()
const results = data.results ?? []
const matchesWithCorrectHash = results.filter((each) => each.digest === `sha256:${hashVersion}` || (each.images || []).some((each) => each.digest === `sha256:${hashVersion}`))
const usefulMatches = matchesWithCorrectHash.filter(each=>each.name!=tag)
if (usefulMatches.length > 0) {
console.log(`Found ${usefulMatches.length} matches:`)
for (const each of usefulMatches) {
console.log(` \x1b[34mFROM docker.io/${namespace}/${repo}:${each.name}\x1b[0m # last updated: ${monthsAgo(each.last_updated)} months ago`)
}
if (confirm("\nThere could be more matches on other pages. Should I STOP here?")) {
Deno.exit(0)
}
}
}
// helper function
function monthsAgo(dateString) {
const pastDate = new Date(dateString)
const today = new Date()
let yearsDiff = today.getFullYear() - pastDate.getFullYear()
let monthsDiff = today.getMonth() - pastDate.getMonth()
let totalMonths = yearsDiff * 12 + monthsDiff
// If the day of the month hasn't been reached yet, subtract one month
if (today.getDate() < pastDate.getDate()) {
totalMonths -= 1
}
return totalMonths
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Anyway, for posterity, here's my iteration of the script, with simplistic command line argument handling, sleeping every 100 pages and adding the
sha256:prefix if not given.