#!/usr/bin/env bash12# SPDX-FileCopyrightText: Copyright The Lima Authors3# SPDX-License-Identifier: Apache-2.045set -eu -o pipefail67# Functions in this script assume error handling with 'set -e'.8# To ensure 'set -e' works correctly:9# - Use 'set +e' before assignments and '$(set -e; <function>)' to capture output without exiting on errors.10# - Avoid calling functions directly in conditions to prevent disabling 'set -e'.11# - Use 'shopt -s inherit_errexit' (Bash 4.4+) to avoid repeated 'set -e' in all '$(...)'.12shopt -s inherit_errexit || error_exit "inherit_errexit not supported. Please use bash 4.4 or later."1314function print_help() {15cat <<HELP16$(basename "${BASH_SOURCE[0]}"): Update the image location in the specified templates1718Usage:19$(basename "${BASH_SOURCE[0]}") <template.yaml>...2021Description:22This script updates the image location in the specified templates.23If the image location in the template contains a release date in the URL, the script replaces it with the latest available date.2425Examples:26Update the Ubuntu image location in templates/**.yaml:27$ $(basename "${BASH_SOURCE[0]}") templates/**.yaml2829Update the Ubuntu image location in ~/.lima/ubuntu/lima.yaml:30$ $(basename "${BASH_SOURCE[0]}") ~/.lima/ubuntu/lima.yaml31$ limactl factory-reset ubuntu3233Flags:34-h, --help Print this help message35HELP36}3738# json prints the JSON object with the given arguments.39# json [key value ...]40# if the value is empty, both key and value are omitted.41# e.g.42# ```console43# json location https://cloud-images.ubuntu.com/minimal/releases/24.04/release-20210914/ubuntu-24.04-minimal-cloudimg-amd64.img arch amd64 digest sha256:...44# {"location":"https://cloud-images.ubuntu.com/minimal/releases/24.04/release-20210914/ubuntu-24.04-minimal-cloudimg-amd64.img","arch":"amd64","digest":"sha256:..."}45# ```46function json() {47local args=() pattern='^(\[.*\]|\{.*\}|true|false|[0-9]+)$' value48[[ ! -p /dev/stdin ]] && args+=(--null-input)49while [[ $# -gt 0 ]]; do50value="${2-}"51if [[ ${value} =~ ${pattern} ]]; then52args+=(--argjson "${1}" "${value}")53elif [[ -n ${value} ]]; then54args+=(--arg "${1}" "${value}")55fi # omit empty values56shift57shift # shift 2 does not work when $# is 158done59jq -c "${args[@]}" '. + $ARGS.named | if . == {} then empty else . end'60}6162# json_vars prints the JSON object with the given variable names.63# e.g.64# ```console65# location=https://cloud-images.ubuntu.com/minimal/releases/24.04/release-20210914/ubuntu-24.04-minimal-cloudimg-amd64.img66# arch=amd6467# digest=sha256:...68# json_vars location arch digest69# {"location":"https://cloud-images.ubuntu.com/minimal/releases/24.04/release-20210914/ubuntu-24.04-minimal-cloudimg-amd64.img","arch":"amd64","digest":"sha256:..."}70# ```71function json_vars() {72local args=() var73for var in "$@"; do74[[ -v ${var} ]] || error_exit "${var} is not set"75args+=("${var}" "${!var}")76done77json "${args[@]}"78}7980# limayaml_arch prints the arch in the lima.yaml format81function limayaml_arch() {82local arch=$183arch=${arch/amd64/x86_64}84arch=${arch/arm64/aarch64}85arch=${arch/armhf/armv7l}86arch=${arch/ppc64el/ppc64le}87echo "${arch}"88}8990function validate_boolean() {91local value=$192case "${value}" in93'') ;;94true | 1) echo true ;;95false | 0) echo false ;;96*) error_exit "Invalid boolean value: ${value}" ;;97esac98}99100# validate_url checks if the URL is valid and prints the location if it is.101# If the URL is redirected, it prints the redirected location.102# e.g.103# ```console104# validate_url https://cloud-images.ubuntu.com/server/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img105# https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img106# ```107function validate_url() {108local url=$1109code_location=$(curl -sSL -o /dev/null -I -w "%{http_code}\t%{url_effective}" "${url}")110read -r code location <<<"${code_location}"111[[ ${code} -eq 200 ]] || error_exit "[${code}]: The image is not available for download from ${location}"112echo "${location}"113}114115# validate_url_without_redirect checks if the URL is valid and prints the location if it is.116# If the URL is redirected, it prints the URL before the redirection.117# e.g.118# ```console119# validate_url_without_redirect https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-arm64.qcow2120# https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-arm64.qcow2121# ```122# cloud.debian.org may be redirected to other domains(e.g. chuangtzu.ftp.acc.umu.se), but we want to use the original URL.123function validate_url_without_redirect() {124local url=$1 location125location=$(validate_url "${url}")126[[ -n ${location} ]] || error_exit "The image is not available for download from ${url}"127echo "${url}"128}129130# check if the script is executed or sourced131# shellcheck disable=SC1091132if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then133scriptdir=$(dirname "${BASH_SOURCE[0]}")134# shellcheck source=./cache-common-inc.sh135. "${scriptdir}/cache-common-inc.sh"136137# Scripts for each distribution are expected to:138# - Add their identifier to the SUPPORTED_DISTRIBUTIONS array.139# - Register the following functions:140# - ${distribution}_cache_key_for_image_kernel141# - Arguments: location, kernel_location142# - Returns: cache_key (string)143# - Exits with an error if the image location is not supported.144# - ${distribution}_image_entry_for_image_kernel145# - Arguments: location, kernel_location146# - Returns: image_entry (JSON object)147# - Exits with an error if the image location is not supported.148declare -a SUPPORTED_DISTRIBUTIONS=()149150# shellcheck source=./update-template-ubuntu.sh151. "${scriptdir}/update-template-ubuntu.sh"152# shellcheck source=./update-template-debian.sh153. "${scriptdir}/update-template-debian.sh"154# shellcheck source=./update-template-archlinux.sh155. "${scriptdir}/update-template-archlinux.sh"156# shellcheck source=./update-template-centos-stream.sh157. "${scriptdir}/update-template-centos-stream.sh"158# shellcheck source=./update-template-almalinux.sh159. "${scriptdir}/update-template-almalinux.sh"160# shellcheck source=./update-template-almalinux-kitten.sh161. "${scriptdir}/update-template-almalinux-kitten.sh"162# shellcheck source=./update-template-rocky.sh163. "${scriptdir}/update-template-rocky.sh"164# shellcheck source=./update-template-alpine.sh165. "${scriptdir}/update-template-alpine.sh"166# shellcheck source=./update-template-oraclelinux.sh167. "${scriptdir}/update-template-oraclelinux.sh"168# shellcheck source=./update-template-fedora.sh169. "${scriptdir}/update-template-fedora.sh"170# shellcheck source=./update-template-opensuse.sh171. "${scriptdir}/update-template-opensuse.sh"172else173# this script is sourced174return 0175fi176177declare -a templates=()178while [[ $# -gt 0 ]]; do179case "$1" in180-h | --help)181print_help182exit 0183;;184-d | --debug) set -x ;;185*.yaml) templates+=("$1") ;;186*)187error_exit "Unknown argument: $1"188;;189esac190shift191done192193if [[ ${#templates[@]} -eq 0 ]]; then194print_help195exit 0196fi197198declare -a distributions=()199# Check if the distribution has the required functions200for distribution in "${SUPPORTED_DISTRIBUTIONS[@]}"; do201if declare -f "${distribution}_cache_key_for_image_kernel" >/dev/null &&202declare -f "${distribution}_image_entry_for_image_kernel" >/dev/null; then203distributions+=("${distribution}")204fi205done206[[ ${#distributions[@]} -gt 0 ]] || error_exit "No supported distributions found"207208declare -A image_entry_cache=()209210for template in "${templates[@]}"; do211echo "Processing ${template}"212# 1. extract location by parsing template using arch213yq_filter="214.images[] | [.location, .kernel.location, .kernel.cmdline] | @tsv215"216parsed=$(yq eval "${yq_filter}" "${template}")217218# 3. get the image location219arr=()220while IFS= read -r line; do arr+=("${line}"); done <<<"${parsed}"221locations=("${arr[@]}")222for ((index = 0; index < ${#locations[@]}; index++)); do223[[ ${locations[index]} != "null" ]] || continue224set -e225IFS=$'\t' read -r location kernel_location kernel_cmdline <<<"${locations[index]}"226for distribution in "${distributions[@]}"; do227set +e # Disable 'set -e' to avoid exiting on error for the next assignment.228cache_key=$(229set -e # Enable 'set -e' for the next command.230"${distribution}_cache_key_for_image_kernel" "${location}" "${kernel_location}"231) # Check exit status separately to prevent disabling 'set -e' by using the function call in the condition.232# shellcheck disable=2181233[[ $? -eq 0 ]] || continue234image_entry=$(235set -e # Enable 'set -e' for the next command.236if [[ -v image_entry_cache[${cache_key}] ]]; then237echo "${image_entry_cache[${cache_key}]}"238else239"${distribution}_image_entry_for_image_kernel" "${location}" "${kernel_location}"240fi241) # Check exit status separately to prevent disabling 'set -e' by using the function call in the condition.242# shellcheck disable=2181243[[ $? -eq 0 ]] || continue244set -e245image_entry_cache[${cache_key}]="${image_entry}"246if [[ -n ${image_entry} ]]; then247[[ ${kernel_cmdline} != "null" ]] &&248jq -e 'has("kernel")' <<<"${image_entry}" >/dev/null &&249image_entry=$(jq ".kernel.cmdline = \"${kernel_cmdline}\"" <<<"${image_entry}")250echo "${image_entry}" | jq251limactl edit --log-level error --set "252.images[${index}] = ${image_entry}|253(.images[${index}] | ..) style = \"double\"254" "${template}"255fi256done257done258done259260261