From ee9d901d2bb8ac776e8b5b63d09f5727b694b086 Mon Sep 17 00:00:00 2001 From: Jea-Eok-Kim Date: Tue, 8 Sep 2026 17:11:06 +0900 Subject: [PATCH] Renew the registry token when a layer download fails mid-import The token is fetched once before the layers are downloaded, and its lifetime is set by the registry: 600 s on nvcr.io, 300 s on Docker Hub. A blob request answers 307 and the transfer itself runs against a signed CDN URL, so what matters is when each layer's redirect is requested. On an image with many layers, a request issued near the end of a long download lands after the token has expired and fails with 401. Neither retry layer can recover from that. curl --retry does not retry a 401, and the parallel --retries pass re-reads the same -K file, which nothing has refreshed. Renew the token and resume instead. Layers already in the cache exit early on the next pass, so the download continues where it stopped, and resuming only while the cache keeps growing leaves failures with any other cause to stop as they do today. Signed-off-by: Jea-Eok-Kim --- src/docker.sh | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/docker.sh b/src/docker.sh index a1842d3..f25d494 100644 --- a/src/docker.sh +++ b/src/docker.sh @@ -136,7 +136,7 @@ docker::_download() { local image="$3" local req_params=() layers=() layer_media_types=() missing_digests=() missing_media_types=() - local manifest= config= digest= media_type= idx= + local manifest= config= digest= media_type= idx= rv= cached= cached_prev=-1 local accept_manifest_list=("-H" "Accept: application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.index.v1+json") local accept_manifest=("-H" "Accept: application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json") local url_manifest="${curl_proto}://${registry}/v2/${image}/manifests/${tag}" @@ -185,9 +185,31 @@ docker::_download() { # Download digests, verify their checksums and extract them in the cache. if [ "${#missing_digests[@]}" -gt 0 ]; then common::log INFO "Downloading ${#missing_digests[@]} missing layers..." NL - BASH_ENV="${BASH_SOURCE[0]}" parallel --plain ${TTY_ON+--bar} --xapply --shuf --retries 2 -j "${ENROOT_MAX_CONNECTIONS}" -q \ - docker::_download_extract "{1}" "{2}" "${curl_opts[@]}" -f "${req_params[@]}" -- "${url_digest}sha256:{1}" ::: "${missing_digests[@]}" ::: "${missing_media_types[@]}" - common::log + while :; do + rv=0 + BASH_ENV="${BASH_SOURCE[0]}" parallel --plain ${TTY_ON+--bar} --xapply --shuf --retries 2 -j "${ENROOT_MAX_CONNECTIONS}" -q \ + docker::_download_extract "{1}" "{2}" "${curl_opts[@]}" -f "${req_params[@]}" -- "${url_digest}sha256:{1}" ::: "${missing_digests[@]}" ::: "${missing_media_types[@]}" || rv=$? + common::log + [ "${rv}" -eq 0 ] && break + + # The registry token is fetched once above and can expire before the + # last layer is requested, which fails the download with a 401 that + # neither curl nor parallel can retry into a success. Renew it and + # resume: layers already in the cache exit early on the next pass. + # Only resume while the cache keeps growing, so a download that is + # failing for any other reason still stops. + cached=0 + for digest in "${layers[@]}"; do + [ -e "${ENROOT_CACHE_PATH}/${digest}" ] && cached=$((cached + 1)) + done + if [ "${cached}" -le "${cached_prev}" ]; then + common::err "Could not download all the layers of ${registry}/${image}" + fi + cached_prev="${cached}" + + common::log INFO "Download interrupted, renewing the registry token and resuming" + docker::_authenticate "${user}" "${registry}" "${url_manifest}" + done else common::log INFO "Found all layers in cache" fi