"""HEAD each ref@digest at its registry (anonymous token). HEAD is not a pull and does not count against Docker Hub's pull limit. Prints one line per ref: status + registry answer.""" import json, sys, urllib.request, urllib.error, re ACC = "application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.docker.distribution.manifest.v2+json,application/vnd.oci.image.manifest.v1+json" def split(ref): parts = ref.split("/") if len(parts) == 1 or ("." not in parts[0] and ":" not in parts[0] and parts[0] != "localhost"): reg, repo = "registry-1.docker.io", ref if "/" not in repo: repo = "library/" + repo else: reg, repo = parts[0], "/".join(parts[1:]) if reg == "docker.io": reg = "registry-1.docker.io" repo = repo.split(":")[0] if ":" in repo.split("/")[-1] else repo return reg, repo def head(reg, repo, dig, token=None): req = urllib.request.Request(f"https://{reg}/v2/{repo}/manifests/{dig}", method="HEAD", headers={"Accept": ACC}) if token: req.add_header("Authorization", "Bearer " + token) try: with urllib.request.urlopen(req, timeout=20) as r: return r.status, r.headers except urllib.error.HTTPError as e: return e.code, e.headers def token_for(hdrs, repo): a = hdrs.get("www-authenticate") or "" m = dict(re.findall(r'(\w+)="([^"]*)"', a)) if "realm" not in m: return None url = f'{m["realm"]}?service={m.get("service","")}&scope=repository:{repo}:pull' with urllib.request.urlopen(url, timeout=20) as r: d = json.load(r) return d.get("token") or d.get("access_token") data = json.load(open(sys.argv[1])) for ref, dig, srcs in data["with_digest"]: reg, repo = split(ref) try: st, h = head(reg, repo, dig) if st == 401: st, h = head(reg, repo, dig, token_for(h, repo)) print(f"{st}\t{ref}@{dig[:19]}\t{','.join(srcs)}", flush=True) except Exception as e: print(f"ERR\t{ref}@{dig[:19]}\t{e}", flush=True)