-
-
Save achetronic/2db363e6c2fbecd42ae67512fbea50ca to your computer and use it in GitHub Desktop.
| #!/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 |
Putting it together:
SHA256_HASH='5bb4faffc8b35e2702b2ffa78e982b979d7b66db29bd55b0c58de8fa745df661'
NAMESPACE='apache'
REPO_NAME='superset'
for i in {1..1000}; do
if [ $i -eq 100 ]; then
echo -e "\e[35mSleeping for 7 seconds on page $i...\e[0m"
sleep 7
fi
echo "Looking into page: $i"
result=$(curl -s "https://registry.hub.docker.com/v2/repositories/$NAMESPACE/$REPO_NAME/tags/?page=$i" | jq -r ".results[] | select(.[\"images\"][][\"digest\"] == \"sha256:$hash\" or .digest == \"sha256:$SHA256_HASH\")") || break
if [ ! -z "$result" ]; then
echo "$result" | jq '.'
break
fi
doneFor official images like https://hub.docker.com/_/node the namespace is "library".
Also, I think @Kyu meant for $hash to be $SHA256_HASH. Fixed:
#!/bin/bash
SHA256_HASH="5bb4faffc8b35e2702b2ffa78e982b979d7b66db29bd55b0c58de8fa745df661"
NAMESPACE='apache'
REPO_NAME='superset'
for i in {1..1000}; do
if [ $i -eq 100 ]; then
echo -e "\e[35mSleeping for 7 seconds on page $i...\e[0m"
sleep 7
fi
echo "Looking into page: $i"
result=$(
curl -s "https://registry.hub.docker.com/v2/repositories/$NAMESPACE/$REPO_NAME/tags/?page=$i" \
| jq -r ".results[] | select(.[\"images\"][][\"digest\"] == \"sha256:$SHA256_HASH\" or .digest == \"sha256:$SHA256_HASH\")"
) || break
if [ ! -z "$result" ]; then
echo "$result" | jq '.'
break
fi
doneFor the benefit of anyone else who happens to hit this, if what you have is the hash of a particular tag of a multi-arch image, this approach does not work.
Ha, the later iterations do work! It's the or .digest == [...] part which does the trick I think.
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.
#!/bin/bash
set -eou pipefail
SHA256_HASH=""
IMAGE=""
while [[ $# -gt 0 ]]; do
case "$1" in
--hash)
shift
SHA256_HASH="$1"
shift
;;
--image)
shift
IMAGE="$1"
shift
;;
*)
echo "Unknown argument \"$1\"" >&2
echo "Usage: $0 --hash HASH --image REPO/IMAGE" >&2
exit 1
;;
esac
done
if [[ -z "$SHA256_HASH" || -z "$IMAGE" ]]; then
echo "Usage: $0 --hash HASH --image REPO/IMAGE" >&2
exit 1
fi
if [[ ! "${SHA256_HASH:0:7}" == "sha256:" ]]; then
SHA256_HASH="sha256:${SHA256_HASH}"
fi
for i in {1..1000}; do
if [[ $(expr $i % 100) -eq 0 ]]; then
echo "Sleeping for 7 seconds on page $i..."
sleep 7
fi
echo "Looking into page: $i"
result=$(
curl "https://registry.hub.docker.com/v2/repositories/$IMAGE/tags/?page=$i" |
jq '.results[] | select(.["images"][]["digest"] == "'${SHA256_HASH}'" or .digest == "'${SHA256_HASH}'")'
) || break
if [[ ! -z "$result" ]]; then
echo "$result" | jq .
break
fi
doneContinuing 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
}
@ThomDietrich @aryehb Thank you for the contribution mates!
This is used more as a life-saver trick when you only have the sha256 and you need to find the tag pointing to that digest in the repo to fix it in your deployment manifests