|
| 1 | +#!/usr/bin/env bash |
| 2 | + |
| 3 | +set -eu -o pipefail |
| 4 | + |
| 5 | +# Functions in this script assume error handling with 'set -e'. |
| 6 | +# To ensure 'set -e' works correctly: |
| 7 | +# - Use 'set +e' before assignments and '$(set -e; <function>)' to capture output without exiting on errors. |
| 8 | +# - Avoid calling functions directly in conditions to prevent disabling 'set -e'. |
| 9 | +# - Use 'shopt -s inherit_errexit' (Bash 4.4+) to avoid repeated 'set -e' in all '$(...)'. |
| 10 | +shopt -s inherit_errexit || error_exit "inherit_errexit not supported. Please use bash 4.4 or later." |
| 11 | + |
| 12 | +function print_help() { |
| 13 | + cat <<HELP |
| 14 | +$(basename "${BASH_SOURCE[0]}"): Update the image location in the specified templates |
| 15 | +
|
| 16 | +Usage: |
| 17 | + $(basename "${BASH_SOURCE[0]}") <template.yaml>... |
| 18 | +
|
| 19 | +Description: |
| 20 | + This script updates the image location in the specified templates. |
| 21 | + If the image location in the template contains a release date in the URL, the script replaces it with the latest available date. |
| 22 | +
|
| 23 | +Examples: |
| 24 | + Update the Ubuntu image location in templates/**.yaml: |
| 25 | + $ $(basename "${BASH_SOURCE[0]}") templates/**.yaml |
| 26 | +
|
| 27 | + Update the Ubuntu image location in ~/.lima/ubuntu/lima.yaml: |
| 28 | + $ $(basename "${BASH_SOURCE[0]}") ~/.lima/ubuntu/lima.yaml |
| 29 | +
|
| 30 | +Flags: |
| 31 | + -h, --help Print this help message |
| 32 | +HELP |
| 33 | +} |
| 34 | + |
| 35 | +# json prints the JSON object with the given arguments. |
| 36 | +# json [key value ...] |
| 37 | +# if the value is empty, both key and value are omitted. |
| 38 | +# e.g. |
| 39 | +# ```console |
| 40 | +# json location https://cloud-images.ubuntu.com/minimal/releases/24.04/release-20210914/ubuntu-24.04-minimal-cloudimg-amd64.img arch amd64 digest sha256:... |
| 41 | +# {"location":"https://cloud-images.ubuntu.com/minimal/releases/24.04/release-20210914/ubuntu-24.04-minimal-cloudimg-amd64.img","arch":"amd64","digest":"sha256:..."} |
| 42 | +# ``` |
| 43 | +function json() { |
| 44 | + local args=() pattern='^(\[.*\]|\{.*\})$' value |
| 45 | + [[ ! -p /dev/stdin ]] && args+=(--null-input) |
| 46 | + while [[ $# -gt 0 ]]; do |
| 47 | + value="${2-}" |
| 48 | + if [[ ${value} =~ ${pattern} ]]; then |
| 49 | + args+=(--argjson "${1}" "${value}") |
| 50 | + elif [[ -n ${value} ]]; then |
| 51 | + args+=(--arg "${1}" "${value}") |
| 52 | + fi # omit empty values |
| 53 | + shift |
| 54 | + shift # shift 2 does not work when $# is 1 |
| 55 | + done |
| 56 | + jq -c "${args[@]}" '. + $ARGS.named | if . == {} then empty else . end' |
| 57 | +} |
| 58 | + |
| 59 | +# json_vars prints the JSON object with the given variable names. |
| 60 | +# e.g. |
| 61 | +# ```console |
| 62 | +# location=https://cloud-images.ubuntu.com/minimal/releases/24.04/release-20210914/ubuntu-24.04-minimal-cloudimg-amd64.img |
| 63 | +# arch=amd64 |
| 64 | +# digest=sha256:... |
| 65 | +# json_vars location arch digest |
| 66 | +# {"location":"https://cloud-images.ubuntu.com/minimal/releases/24.04/release-20210914/ubuntu-24.04-minimal-cloudimg-amd64.img","arch":"amd64","digest":"sha256:..."} |
| 67 | +# ``` |
| 68 | +function json_vars() { |
| 69 | + local args=() var |
| 70 | + for var in "$@"; do |
| 71 | + [[ -v ${var} ]] || error_exit "${var} is not set" |
| 72 | + args+=("${var}" "${!var}") |
| 73 | + done |
| 74 | + json "${args[@]}" |
| 75 | +} |
| 76 | + |
| 77 | +# limayaml_arch returns the arch in the lima.yaml format |
| 78 | +function limayaml_arch() { |
| 79 | + local arch=$1 |
| 80 | + arch=${arch/amd64/x86_64} |
| 81 | + arch=${arch/arm64/aarch64} |
| 82 | + arch=${arch/armhf/armv7l} |
| 83 | + echo "${arch}" |
| 84 | +} |
| 85 | + |
| 86 | +# validate_url checks if the URL is valid and returns the location if it is. |
| 87 | +# If the URL is redirected, it returns the redirected location. |
| 88 | +# e.g. |
| 89 | +# ```console |
| 90 | +# validate_url https://cloud-images.ubuntu.com/server/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img |
| 91 | +# https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img |
| 92 | +# ``` |
| 93 | +function validate_url() { |
| 94 | + local url=$1 |
| 95 | + code_location=$(curl -sSL -o /dev/null -I -w "%{http_code}\t%{url_effective}" "${url}") |
| 96 | + read -r code location <<<"${code_location}" |
| 97 | + [[ ${code} -eq 200 ]] || error_exit "[${code}]: The image is not available for download from ${location}" |
| 98 | + echo "${location}" |
| 99 | +} |
| 100 | + |
| 101 | +# check if the script is executed or sourced |
| 102 | +# shellcheck disable=SC1091 |
| 103 | +if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then |
| 104 | + scriptdir=$(dirname "${BASH_SOURCE[0]}") |
| 105 | + # shellcheck source=./cache-common-inc.sh |
| 106 | + . "${scriptdir}/cache-common-inc.sh" |
| 107 | + |
| 108 | + # Scripts for each distribution are expected to: |
| 109 | + # - Add their identifier to the SUPPORTED_DISTRIBUTIONS array. |
| 110 | + # - Register the following functions: |
| 111 | + # - ${distribution}_cache_key_for_image_kernel |
| 112 | + # - Arguments: location, kernel_location |
| 113 | + # - Returns: cache_key (string) |
| 114 | + # - Exits with an error if the image location is not supported. |
| 115 | + # - ${distribution}_image_entry_for_image_kernel |
| 116 | + # - Arguments: location, kernel_location |
| 117 | + # - Returns: image_entry (JSON object) |
| 118 | + # - Exits with an error if the image location is not supported. |
| 119 | + declare -a SUPPORTED_DISTRIBUTIONS=() |
| 120 | + |
| 121 | + # shellcheck source=./update-template-ubuntu.sh |
| 122 | + . "${scriptdir}/update-template-ubuntu.sh" |
| 123 | +else |
| 124 | + # this script is sourced |
| 125 | + return 0 |
| 126 | +fi |
| 127 | + |
| 128 | +declare -a templates=() |
| 129 | +while [[ $# -gt 0 ]]; do |
| 130 | + case "$1" in |
| 131 | + -h | --help) |
| 132 | + print_help |
| 133 | + exit 0 |
| 134 | + ;; |
| 135 | + -d | --debug) set -x ;; |
| 136 | + *.yaml) templates+=("$1") ;; |
| 137 | + *) |
| 138 | + error_exit "Unknown argument: $1" |
| 139 | + ;; |
| 140 | + esac |
| 141 | + shift |
| 142 | +done |
| 143 | + |
| 144 | +if [[ ${#templates[@]} -eq 0 ]]; then |
| 145 | + print_help |
| 146 | + exit 0 |
| 147 | +fi |
| 148 | + |
| 149 | +declare -a distributions=() |
| 150 | +# Check if the distribution has the required functions |
| 151 | +for distribution in "${SUPPORTED_DISTRIBUTIONS[@]}"; do |
| 152 | + if declare -f "${distribution}_cache_key_for_image_kernel" >/dev/null && |
| 153 | + declare -f "${distribution}_image_entry_for_image_kernel" >/dev/null; then |
| 154 | + distributions+=("${distribution}") |
| 155 | + fi |
| 156 | +done |
| 157 | +[[ ${#distributions[@]} -gt 0 ]] || error_exit "No supported distributions found" |
| 158 | + |
| 159 | +declare -A image_entry_cache=() |
| 160 | + |
| 161 | +for template in "${templates[@]}"; do |
| 162 | + echo "Processing ${template}" |
| 163 | + # 1. extract location by parsing template using arch |
| 164 | + yq_filter=" |
| 165 | + .images[] | [.location, .kernel.location, .kernel.cmdline] | @tsv |
| 166 | + " |
| 167 | + parsed=$(yq eval "${yq_filter}" "${template}") |
| 168 | + |
| 169 | + # 3. get the image location |
| 170 | + arr=() |
| 171 | + while IFS= read -r line; do arr+=("${line}"); done <<<"${parsed}" |
| 172 | + locations=("${arr[@]}") |
| 173 | + for ((index = 0; index < ${#locations[@]}; index++)); do |
| 174 | + [[ ${locations[index]} != "null" ]] || continue |
| 175 | + set -e |
| 176 | + IFS=$'\t' read -r location kernel_location kernel_cmdline <<<"${locations[index]}" |
| 177 | + for distribution in "${distributions[@]}"; do |
| 178 | + set +e # Disable 'set -e' to avoid exiting on error for the next assignment. |
| 179 | + cache_key=$( |
| 180 | + set -e # Enable 'set -e' for the next command. |
| 181 | + "${distribution}_cache_key_for_image_kernel" "${location}" "${kernel_location}" |
| 182 | + ) # Check exit status separately to prevent disabling 'set -e' by using the function call in the condition. |
| 183 | + # shellcheck disable=2181 |
| 184 | + [[ $? -eq 0 ]] || continue |
| 185 | + image_entry=$( |
| 186 | + set -e # Enable 'set -e' for the next command. |
| 187 | + if [[ -v image_entry_cache[${cache_key}] ]]; then |
| 188 | + echo "${image_entry_cache[${cache_key}]}" |
| 189 | + else |
| 190 | + "${distribution}_image_entry_for_image_kernel" "${location}" "${kernel_location}" |
| 191 | + fi |
| 192 | + ) # Check exit status separately to prevent disabling 'set -e' by using the function call in the condition. |
| 193 | + # shellcheck disable=2181 |
| 194 | + [[ $? -eq 0 ]] || continue |
| 195 | + set -e |
| 196 | + image_entry_cache[${cache_key}]="${image_entry}" |
| 197 | + if [[ -n ${image_entry} ]]; then |
| 198 | + [[ ${kernel_cmdline} != "null" ]] && |
| 199 | + jq -e 'has("kernel")' <<<"${image_entry}" >/dev/null && |
| 200 | + image_entry=$(jq ".kernel.cmdline = \"${kernel_cmdline}\"" <<<"${image_entry}") |
| 201 | + echo "${image_entry}" | jq |
| 202 | + limactl edit --log-level error --set " |
| 203 | + .images[${index}] = ${image_entry}| |
| 204 | + (.images[${index}] | ..) style = \"double\" |
| 205 | + " "${template}" |
| 206 | + fi |
| 207 | + done |
| 208 | + done |
| 209 | +done |
0 commit comments