diff --git a/doc/image-format.md b/doc/image-format.md index 6af0686..8888011 100644 --- a/doc/image-format.md +++ b/doc/image-format.md @@ -34,3 +34,10 @@ ${PWD} /mnt none x-create=dir,bind # Example preserving the DISPLAY environment variable from the host DISPLAY=${DISPLAY} ``` + +Images produced by [import](cmd/import.md) also include a provenance file at `/.enroot/source` recording the URI the image was imported from. Registry imports are recorded in enroot's canonical `docker://REGISTRY#IMAGE:TAG` form without credentials; daemon imports keep their `dockerd://` or `podman://` URI. The file is purely informational and does not affect runtime behavior; it is not updated if the image is modified and re-exported. + +```sh +# Example contents +uri=docker://nvcr.io#nvidia/pytorch:25.06-py3 +``` diff --git a/src/docker.sh b/src/docker.sh index f67e61d..c5c002f 100644 --- a/src/docker.sh +++ b/src/docker.sh @@ -303,6 +303,24 @@ docker::_parse_uri() { printf "%s\n%s\n%s\n%s\n" "${user}" "${registry}" "${image}" "${tag}" } +docker::_format_uri() { + local -r registry="$1" image="$2" tag="$3" + + if [[ "${tag}" =~ ^sha256: ]]; then + printf "docker://%s#%s@%s\n" "${registry}" "${image}" "${tag}" + else + printf "docker://%s#%s:%s\n" "${registry}" "${image}" "${tag}" + fi +} + +docker::_record_source() { + local -r rootfs="$1" uri="$2" + local -r source_meta="${rootfs}/.enroot/source" + + mkdir -p "${source_meta%/*}" + printf 'uri=%s\n' "${uri}" > "${source_meta}" +} + docker::_prepare_layers() ( local -r user="$1" registry="$2" image="$3" tag="$4" arch="$5" local layers=() config= @@ -439,7 +457,7 @@ docker::digest() ( docker::import() ( local -r uri="$1" local filename="$2" arch="$3" - local user= registry= image= tag= tmpdir= timestamp=() config= layer_count= + local user= registry= image= tag= tmpdir= timestamp=() config= layer_count= source_uri= common::checkcmd curl grep awk jq parallel tar "${ENROOT_GZIP_PROGRAM}" find mksquashfs zstd @@ -471,8 +489,10 @@ docker::import() ( common::chdir "${tmpdir}" # Prepare layers and configure rootfs. + source_uri=$(docker::_format_uri "${registry}" "${image}" "${tag}") docker::_prepare_layers "${user}" "${registry}" "${image}" "${tag}" "${arch}" \ | { common::read -r config; common::read -r layer_count; } + docker::_record_source "${PWD}/0" "${source_uri}" if [ -n "${SOURCE_DATE_EPOCH-}" ]; then timestamp=("-mkfs-time" "${SOURCE_DATE_EPOCH}" "-all-time" "${SOURCE_DATE_EPOCH}") @@ -488,7 +508,7 @@ docker::import() ( docker::load() ( local -r uri="$1" local name="$2" arch="$3" - local user= registry= image= tag= tmpdir= config= layer_count= + local user= registry= image= tag= tmpdir= config= layer_count= source_uri= if [ -z "${ENROOT_NATIVE_OVERLAYFS-}" ]; then common::err "ENROOT_NATIVE_OVERLAYFS=y is required for enroot load" @@ -529,8 +549,10 @@ docker::load() ( common::chdir "${tmpdir}" # Prepare layers and configure rootfs. + source_uri=$(docker::_format_uri "${registry}" "${image}" "${tag}") ENROOT_SET_USER_XATTRS=y docker::_prepare_layers "${user}" "${registry}" "${image}" "${tag}" "${arch}" \ | { common::read -r config; common::read -r layer_count; } + docker::_record_source "${PWD}/0" "${source_uri}" # Create the final filesystem by overlaying all the layers and copying to target rootfs. common::log INFO "Loading container root filesystem..." NL @@ -602,6 +624,7 @@ docker::daemon::import() ( common::fixperms rootfs "${engine}" inspect "${image}" | common::jq '.[] | with_entries(.key|=ascii_downcase)' > config docker::configure rootfs config "${arch}" + docker::_record_source rootfs "${uri}" # Create the final squashfs filesystem. common::log INFO "Creating squashfs filesystem..." NL