#!/usr/bin/env bash12# SPDX-FileCopyrightText: Copyright The Lima Authors3# SPDX-License-Identifier: Apache-2.045# print the error message and exit with status 16function error_exit() {7echo "Error: $*" >&28exit 19}1011# e.g.12# ```console13# $ download_template_if_needed templates/default.yaml14# templates/default.yaml15# $ download_template_if_needed https://raw.githubusercontent.com/lima-vm/lima/v0.15.1/examples/ubuntu-lts.yaml16# /tmp/tmp.1J9Q6Q/template.yaml17# ```18function download_template_if_needed() {19local template="$1"20tmp_yaml="$(mktemp -d)/template.yaml"21# The upgrade test doesn't have limactl installed first. The old version wouldn't support `limactl tmpl` anyways.22if command -v limactl >/dev/null; then23limactl tmpl copy --embed-all "${template}" "${tmp_yaml}" || return24else25if [[ $template == https://* ]]; then26curl -sSLf "${template}" >"${tmp_yaml}" || return27else28cp "${template}" "${tmp_yaml}"29fi30fi31echo "${tmp_yaml}"32}3334# e.g.35# ```console36# $ print_image_locations_for_arch_from_template templates/default.yaml37# https://cloud-images.ubuntu.com/releases/24.04/release-20240809/ubuntu-24.04-server-cloudimg-arm64.img sha256:2e0c90562af1970ffff220a5073a7830f4acc2aad55b31593003e8c363381e7a38# https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-arm64.img null39# ```40function print_image_locations_for_arch_from_template() {41local template arch42template=$(download_template_if_needed "$1") || return43local -r template=${template}44arch=$(detect_arch "${template}" "${2:-}") || return45local -r arch=${arch}4647# extract digest, location and size by parsing template using arch48local -r yq_filter="[.images | map(select(.arch == \"${arch}\")) | .[].location] | .[]"49yq eval "${yq_filter}" "${template}"50}5152# e.g.53# ```console54# $ detect_arch templates/default.yaml55# x86_6456# $ detect_arch templates/default.yaml arm6457# aarch6458# ```59function detect_arch() {60local template arch61template=$(download_template_if_needed "$1") || return62local -r template=${template}6364arch="${2:-$(yq '.arch // ""' "${template}")}"65arch="${arch:-$(uname -m)}"66# normalize arch. amd64 -> x86_64, arm64 -> aarch6467case "${arch}" in68amd64 | x86_64) arch=x86_64 ;;69aarch64 | arm64) arch=aarch64 ;;70*) ;;71esac72echo "${arch}"73}7475# e.g.76# ```console77# $ print_image_locations_for_arch_from_template templates/default.yaml|print_valid_image_index78# 079# ```80function print_valid_image_index() {81local index=082while read -r location; do83[[ ${location} != "null" ]] || continue84http_code_and_size=$(check_location_with_cache "${location}")85read -r http_code _size <<<"${http_code_and_size}"86if [[ ${http_code} -eq 200 ]]; then87echo "${index}"88return89fi90index=$((index + 1))91done92echo "Failed to get the valid image location" >&293return 194}9596# e.g.97# ```console98# $ size_from_location "https://cloud-images.ubuntu.com/releases/24.04/release-20240725/ubuntu-24.04-server-cloudimg-amd64.img"99# 585498624100# ```101function size_from_location() {102(103set -o pipefail104local location=$1105check_location "${location}" | cut -d' ' -f2106)107}108109# Check the remote location and print the http code and size.110# If GITHUB_ACTIONS is true, the result is not cached.111# e.g.112# ```console113# $ check_location "https://cloud-images.ubuntu.com/releases/24.04/release-20240725/ubuntu-24.04-server-cloudimg-amd64.img"114# 200 585498624115# ```116function check_location() {117# shellcheck disable=SC2154118if [[ ${GITHUB_ACTIONS:-false} == true ]]; then119check_location_without_cache "$1"120else121check_location_with_cache "$1"122fi123}124125# Check the remote location and print the http code and size.126# The result is cached in .check_location-response-cache.yaml127# e.g.128# ```console129# $ check_location_with_cache "https://cloud-images.ubuntu.com/releases/24.04/release-20240725/ubuntu-24.04-server-cloudimg-amd64.img"130# 200 585498624131# ```132function check_location_with_cache() {133local -r location="$1" cache_file=".check_location-response-cache.yaml"134# check ${cache_file} for the cache135if [[ -f ${cache_file} ]]; then136cached=$(yq -e eval ".[\"${location}\"]" "${cache_file}" 2>/dev/null) && echo "${cached}" && return137else138touch "${cache_file}"139fi140http_code_and_size=$(check_location_without_cache "${location}") || return141yq eval ".[\"${location}\"] = \"${http_code_and_size}\"" -i "${cache_file}" || return142echo "${http_code_and_size}"143}144145# e.g.146# ```console147# $ check_location "https://cloud-images.ubuntu.com/releases/24.04/release-20240725/ubuntu-24.04-server-cloudimg-amd64.img"148# 200 585498624149# ```150function check_location_without_cache() {151local -r location="$1"152curl -sIL -w "%{http_code} %header{Content-Length}" "${location}" -o /dev/null153}154155# e.g.156# ```console157# $ print_image_kernel_initrd_locations_with_digest_for_arch_from_template_at_index templates/default.yaml 0158# https://cloud-images.ubuntu.com/releases/24.04/release-20240809/ubuntu-24.04-server-cloudimg-arm64.img159# sha256:2e0c90562af1970ffff220a5073a7830f4acc2aad55b31593003e8c363381e7a160# null161# null162# null163# null164# ```165function print_image_kernel_initrd_locations_with_digest_for_arch_from_template_at_index() {166local template index="${2:-}" arch167template=$(download_template_if_needed "$1") || return168local -r template=${template}169arch=$(detect_arch "${template}" "${3:-}") || return170local -r arch=${arch}171172local -r yq_filter="[(.images[] | select(.arch == \"${arch}\"))].[${index}]|[173.location,174.digest,175.kernel.location,176.kernel.digest,177.initrd.location,178.initrd.digest179]"180yq -o=t eval "${yq_filter}" "${template}"181}182183# e.g.184# ```console185# $ print_containerd_config_for_arch_from_template templates/default.yaml186# true187# https://github.com/containerd/nerdctl/releases/download/v1.7.6/nerdctl-full-1.7.6-linux-arm64.tar.gz188# sha256:77c747f09853ee3d229d77e8de0dd3c85622537d82be57433dc1fca4493bab95189# ```190function print_containerd_config_for_arch_from_template() {191local template arch192template=$(download_template_if_needed "$1") || return193local -r template=${template}194arch=$(detect_arch "${template}" "${2:-}") || return195local -r arch=${arch}196197local -r yq_filter="198[.containerd|[.system or .user],199.containerd.archives | map(select(.arch == \"${arch}\")) | [.[0].location, .[0].digest]]|flatten200"201validated_template="$(202limactl validate "${template}" --fill 2>/dev/null || echo "{.containerd: {system: false, user: false, archives: []}}"203)"204yq -o=t eval "${yq_filter}" <<<"${validated_template}"205}206207# e.g.208# ```console209# $ location_to_sha256 "https://cloud-images.ubuntu.com/releases/24.04/release-20240809/ubuntu-24.04-server-cloudimg-arm64.img"210# ae988d797c6de06b9c8a81a2b814904151135ccfd4616c22948057f6280477e8211# ```212function location_to_sha256() {213(214set -o pipefail215local -r location="$1"216if command -v sha256sum >/dev/null; then217sha256="$(echo -n "${location}" | sha256sum | cut -d' ' -f1)"218elif command -v shasum >/dev/null; then219sha256="$(echo -n "${location}" | shasum -a 256 | cut -d' ' -f1)"220else221error_exit "sha256sum or shasum not found"222fi223echo "${sha256}"224)225}226227# e.g.228# ```console229# $ cache_download_dir230# .download # on GitHub Actions231# /home/user/.cache/lima/download # on Linux232# /Users/user/Library/Caches/lima/download # on macOS233# /home/user/.cache/lima/download # on others234# ```235function cache_download_dir() {236if [[ ${GITHUB_ACTIONS:-false} == true ]]; then237echo ".download"238else239case "$(uname -s)" in240Linux) echo "${XDG_CACHE_HOME:-${HOME}/.cache}/lima/download" ;;241Darwin) echo "${HOME}/Library/Caches/lima/download" ;;242*) echo "${HOME}/.cache/lima/download" ;;243esac244fi245}246247# e.g.248# ```console249# $ location_to_cache_path "https://cloud-images.ubuntu.com/releases/24.04/release-20240809/ubuntu-24.04-server-cloudimg-arm64.img"250# .download/by-url-sha256/ae988d797c6de06b9c8a81a2b814904151135ccfd4616c22948057f6280477e8251# ```252function location_to_cache_path() {253local location=$1254[[ ${location} != "null" ]] || return255sha256=$(location_to_sha256 "${location}") && download_dir=$(cache_download_dir) && echo "${download_dir}/by-url-sha256/${sha256}"256}257258# e.g.259# ```console260# $ cache_key_from_prefix_location_and_digest image "https://cloud-images.ubuntu.com/releases/24.04/release-20240809/ubuntu-24.04-server-cloudimg-arm64.img" "sha256:2e0c90562af1970ffff220a5073a7830f4acc2aad55b31593003e8c363381e7a"261# image:ubuntu-24.04-server-cloudimg-arm64.img-sha256:2e0c90562af1970ffff220a5073a7830f4acc2aad55b31593003e8c363381e7a262# $ cache_key_from_prefix_location_and_digest image "https://cloud-images.ubuntu.com/releases/24.04/release-20240809/ubuntu-24.04-server-cloudimg-arm64.img" "null"263# image:ubuntu-24.04-server-cloudimg-arm64.img-url-sha256:ae988d797c6de06b9c8a81a2b814904151135ccfd4616c22948057f6280477e8264# ```265function cache_key_from_prefix_location_and_digest() {266local prefix=$1 location=$2 digest=$3 location_basename267[[ ${location} != "null" ]] || return268location_basename=$(basename "${location}")269if [[ ${digest} != "null" ]]; then270echo "${prefix}:${location_basename}-${digest}"271else272# use sha256 of location as key if digest is not available273echo "${prefix}:${location_basename}-url-sha256:$(location_to_sha256 "${location}")"274fi275}276277# e.g.278# ```console279# $ print_path_and_key_for_cache image "https://cloud-images.ubuntu.com/releases/24.04/release-20240809/ubuntu-24.04-server-cloudimg-arm64.img" "sha256:2e0c90562af1970ffff220a5073a7830f4acc2aad55b31593003e8c363381e7a"280# image-path=.download/by-url-sha256/ae988d797c6de06b9c8a81a2b814904151135ccfd4616c22948057f6280477e8281# image-key=image:ubuntu-24.04-server-cloudimg-arm64.img-sha256:2e0c90562af1970ffff220a5073a7830f4acc2aad55b31593003e8c363381e7a282# ```283function print_path_and_key_for_cache() {284local -r prefix=$1 location=$2 digest=$3285cache_path=$(location_to_cache_path "${location}" || true)286cache_key=$(cache_key_from_prefix_location_and_digest "${prefix}" "${location}" "${digest}" || true)287echo "${prefix}-path=${cache_path}"288echo "${prefix}-key=${cache_key}"289}290291# e.g.292# ```console293# $ print_cache_informations_from_template templates/default.yaml294# image-path=.download/by-url-sha256/ae988d797c6de06b9c8a81a2b814904151135ccfd4616c22948057f6280477e8295# image-key=image:ubuntu-24.04-server-cloudimg-arm64.img-sha256:2e0c90562af1970ffff220a5073a7830f4acc2aad55b31593003e8c363381e7a296# kernel-path=297# kernel-key=298# initrd-path=299# initrd-key=300# containerd-path=.download/by-url-sha256/21cc8dfa548ea8a678135bd6984c9feb9f8a01901d10b11bb491f6f4e7537158301# containerd-key=containerd:nerdctl-full-1.7.6-linux-arm64.tar.gz-sha256:77c747f09853ee3d229d77e8de0dd3c85622537d82be57433dc1fca4493bab95302# $ print_cache_informations_from_template templates/experimental/riscv64.yaml303# image-path=.download/by-url-sha256/760b6ec69c801177bdaea06d7ee25bcd6ab72a331b9d3bf38376578164eb8f01304# image-key=image:ubuntu-24.04-server-cloudimg-riscv64.img-sha256:361d72c5ed9781b097ab2dfb1a489c64e51936be648bbc5badee762ebdc50c31305# kernel-path=.download/by-url-sha256/4568026693dc0f31a551b6741839979c607ee6bb0bf7209c89f3348321c52c61306# kernel-key=kernel:qemu-riscv64_smode_uboot.elf-sha256:d4b3a10c3ef04219641802a586dca905e768805f5a5164fb68520887df54f33c307# initrd-path=308# initrd-key=309# ```310function print_cache_informations_from_template() {311(312set -o pipefail313local template index image_kernel_initrd_info location digest containerd_info314template=$(download_template_if_needed "$1") || return315local -r template="${template}"316index=$(print_image_locations_for_arch_from_template "${template}" "${@:2}" | print_valid_image_index) || return317local -r index="${index}"318image_kernel_initrd_info=$(print_image_kernel_initrd_locations_with_digest_for_arch_from_template_at_index "${template}" "${index}" "${@:2}") || return319# shellcheck disable=SC2034320read -r image_location image_digest kernel_location kernel_digest initrd_location initrd_digest <<<"${image_kernel_initrd_info}"321for prefix in image kernel initrd; do322location=${prefix}_location323digest=${prefix}_digest324print_path_and_key_for_cache "${prefix}" "${!location}" "${!digest}"325done326if command -v limactl >/dev/null; then327containerd_info=$(print_containerd_config_for_arch_from_template "${template}" "${@:2}") || return328read -r containerd_enabled containerd_location containerd_digest <<<"${containerd_info}"329if [[ ${containerd_enabled} == "true" ]]; then330print_path_and_key_for_cache "containerd" "${containerd_location}" "${containerd_digest}"331fi332fi333)334}335336# Compatible with hashFile() in GitHub Actions337# e.g.338# ```console339# $ hash_file templates/default.yaml340# ceec5ba3dc8872c083b2eb7f44e3e3f295d5dcdeccf0961ee153be6586525e5e341# ```342function hash_file() {343(344set -o pipefail345local hash=""346for file in "$@"; do347hash="${hash}$(sha256sum "${file}" | cut -d' ' -f1)" || return348done349echo "${hash}" | xxd -r -p | sha256sum | cut -d' ' -f1350)351}352353# Download the file to the cache directory and print the path.354# e.g.355# ```console356# $ download_to_cache "https://cloud-images.ubuntu.com/releases/24.04/release-20240821/ubuntu-24.04-server-cloudimg-arm64.img"357# .download/by-url-sha256/346ee1ff9e381b78ba08e2a29445960b5cd31c51f896fc346b82e26e345a5b9a/data # on GitHub Actions358# /home/user/.cache/lima/download/by-url-sha256/346ee1ff9e381b78ba08e2a29445960b5cd31c51f896fc346b82e26e345a5b9a/data # on Linux359# /Users/user/Library/Caches/lima/download/by-url-sha256/346ee1ff9e381b78ba08e2a29445960b5cd31c51f896fc346b82e26e345a5b9a/data # on macOS360# /home/user/.cache/lima/download/by-url-sha256/346ee1ff9e381b78ba08e2a29445960b5cd31c51f896fc346b82e26e345a5b9a/data # on others361function download_to_cache() {362local cache_path use_redirected_location=${2:-YES}363cache_path=$(location_to_cache_path "$1")364# before checking remote location, check if the data file is already downloaded and the time file is updated within 10 minutes365if [[ -f ${cache_path}/data && -n "$(find "${cache_path}/time" -mmin -10 || true)" ]]; then366echo "${cache_path}/data"367return368fi369370# check the remote location371local curl_info_json write_out372write_out='{373"json":%{json},374"header":%{header_json}375}'376curl_info_json=$(curl -sSLI -w "${write_out}" "$1" -o /dev/null)377378local code time type url379code=$(jq -r '.json.http_code' <<<"${curl_info_json}")380time=$(jq -r '(.header["last-modified"]|first) // (.header["date"]|first) // empty' <<<"${curl_info_json}")381type=$(jq -r '.json.content_type' <<<"${curl_info_json}")382if [[ ${use_redirected_location} == "YES" ]]; then383url=$(jq -r '.json.url_effective' <<<"${curl_info_json}")384else385url=$1386fi387[[ ${code} == 200 ]] || error_exit "Failed to download $1"388389cache_path=$(location_to_cache_path "${url}")390[[ -d ${cache_path} ]] || mkdir -p "${cache_path}"391392local needs_download=0393[[ -f ${cache_path}/data ]] || needs_download=1394[[ -f ${cache_path}/time && "$(<"${cache_path}/time")" == "${time}" ]] || needs_download=1395[[ -f ${cache_path}/type && "$(<"${cache_path}/type")" == "${type}" ]] || needs_download=1396if [[ ${needs_download} -eq 1 ]]; then397curl_info_json=$(398echo "downloading ${url}" >&2399curl -SL -w "${write_out}" --no-clobber -o "${cache_path}/data" "${url}"400)401local filename402code=$(jq -r '.json.http_code' <<<"${curl_info_json}")403time=$(jq -r '(.header["last-modified"]|first) // (.header["date"]|first) // empty' <<<"${curl_info_json}")404type=$(jq -r '.json.content_type' <<<"${curl_info_json}")405url=$(jq -r '.json.url_effective' <<<"${curl_info_json}")406filename=$(jq -r '.json.filename_effective' <<<"${curl_info_json}")407[[ ${code} == 200 ]] || error_exit "Failed to download ${url}"408[[ "${cache_path}/data" == "${filename}" ]] || mv "${filename}" "${cache_path}/data"409# sha256.digest seems existing if expected digest is available. so, not creating it here.410# sha256sum "${cache_path}/data" | awk '{print "sha256:"$1}' >"${cache_path}/sha256.digest"411echo -n "${time}" >"${cache_path}/time"412else413touch "${cache_path}/time"414fi415[[ -f ${cache_path}/type ]] || echo -n "${type}" >"${cache_path}/type"416[[ -f ${cache_path}/url ]] || echo -n "${url}" >"${cache_path}/url"417echo "${cache_path}/data"418}419420# Download the file to the cache directory without redirect and print the path.421function download_to_cache_without_redirect() {422download_to_cache "$1" "NO"423}424425426