Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/hack/update-template-alpine.sh
1637 views
1
#!/usr/bin/env bash
2
3
# SPDX-FileCopyrightText: Copyright The Lima Authors
4
# SPDX-License-Identifier: Apache-2.0
5
6
set -eu -o pipefail
7
8
# Functions in this script assume error handling with 'set -e'.
9
# To ensure 'set -e' works correctly:
10
# - Use 'set +e' before assignments and '$(set -e; <function>)' to capture output without exiting on errors.
11
# - Avoid calling functions directly in conditions to prevent disabling 'set -e'.
12
# - Use 'shopt -s inherit_errexit' (Bash 4.4+) to avoid repeated 'set -e' in all '$(...)'.
13
shopt -s inherit_errexit || error_exit "inherit_errexit not supported. Please use bash 4.4 or later."
14
15
function alpine_print_help() {
16
cat <<HELP
17
$(basename "${BASH_SOURCE[0]}"): Update the Alpine Linux image location in the specified templates
18
19
Usage:
20
$(basename "${BASH_SOURCE[0]}") [--version-major-minor (<major>.<minor>|latest-stable)|--version-major <major> --version-minor <minor>] <template.yaml>...
21
22
Description:
23
This script updates the Alpine Linux image location in the specified templates.
24
Image location basename format:
25
26
<target vendor>_alpine-<version>-<arch>-<firmware>-<bootstrap>[-<machine>]-<image revision>.qcow2
27
28
Published Alpine Linux image information is fetched from the following URLs:
29
30
latest-stable: https://dl-cdn.alpinelinux.org/alpine/latest-stable/releases/cloud
31
<major>.<minor>: https://dl-cdn.alpinelinux.org/alpine/v<major>.<minor>/releases/cloud
32
33
To parsing html, this script requires 'htmlq' or 'pup' command.
34
The downloaded files will be cached in the Lima cache directory.
35
36
Examples:
37
Update the Alpine Linux image location in templates/**.yaml:
38
$ $(basename "${BASH_SOURCE[0]}") templates/**.yaml
39
40
Update the Alpine Linux image location to version 3.18 in ~/.lima/alpine/lima.yaml:
41
$ $(basename "${BASH_SOURCE[0]}") --version-major-minor 3.18 ~/.lima/alpine/lima.yaml
42
$ limactl factory-reset alpine
43
44
Flags:
45
--version-major-minor (<major>.<minor>|latest-stable) Use the specified <major>.<minor> version or alias "latest-stable".
46
The <major>.<minor> version must be 3.18 or later.
47
--version-major <major> --version-minor <minor> Use the specified <major> and <minor> version.
48
-h, --help Print this help message
49
HELP
50
}
51
52
# print the URL spec for the given location
53
function alpine_url_spec_from_location() {
54
local location=$1 jq_filter url_spec
55
jq_filter='capture("
56
^https://dl-cdn\\.alpinelinux\\.org/alpine/(?<path_version>v\\d+\\.\\d+|latest-stable)/releases/cloud/
57
(?<target_vendor>[^_]+)_alpine-(?<version>\\d+\\.\\d+\\.\\d+)-(?<arch>[^-]+)-
58
(?<firmware>[^-]+)-(?<bootstrap>[^-]+)(-(?<machine>metal|vm))?-(?<image_revision>r\\d+)\\.(?<file_extension>.*)$
59
";"x")
60
'
61
url_spec=$(jq -e -r "${jq_filter}" <<<"\"${location}\"")
62
echo "${url_spec}"
63
}
64
65
readonly alpine_jq_filter_directory='"https://dl-cdn.alpinelinux.org/alpine/\(.path_version)/releases/cloud/"'
66
readonly alpine_jq_filter_filename='
67
"\(.target_vendor)_alpine-\(.version)-\(.arch)-\(.firmware)-\(.bootstrap)" +
68
"\(if .machine then "-" + .machine else "" end)-\(.image_revision).\(.file_extension)"
69
'
70
71
# print the location for the given URL spec
72
function alpine_location_from_url_spec() {
73
local -r url_spec=$1
74
jq -e -r "${alpine_jq_filter_directory} + ${alpine_jq_filter_filename}" <<<"${url_spec}" ||
75
error_exit "Failed to get the location for ${url_spec}"
76
}
77
78
function alpine_image_directory_from_url_spec() {
79
local -r url_spec=$1
80
jq -e -r "${alpine_jq_filter_directory}" <<<"${url_spec}" ||
81
error_exit "Failed to get the image directory for ${url_spec}"
82
}
83
84
function alpine_image_filename_from_url_spec() {
85
local -r url_spec=$1
86
jq -e -r "${alpine_jq_filter_filename}" <<<"${url_spec}" ||
87
error_exit "Failed to get the image filename for ${url_spec}"
88
}
89
90
#
91
function alpine_latest_image_entry_for_url_spec() {
92
local url_spec=$1 arch image_directory downloaded_page links_in_page latest_version_info
93
# shellcheck disable=SC2034
94
arch=$(jq -r '.arch' <<<"${url_spec}")
95
image_directory=$(alpine_image_directory_from_url_spec "${url_spec}")
96
downloaded_page=$(download_to_cache "${image_directory}")
97
if command -v htmlq >/dev/null; then
98
links_in_page=$(htmlq 'pre a' --attribute href <"${downloaded_page}")
99
elif command -v pup >/dev/null; then
100
links_in_page=$(pup 'pre a attr{href}' <"${downloaded_page}")
101
else
102
error_exit "Please install 'htmlq' or 'pup' to list images from ${image_directory}"
103
fi
104
latest_version_info=$(jq -e -Rrs --argjson spec "${url_spec}" '
105
[
106
split("\n").[] |
107
capture(
108
"^\($spec.target_vendor)_alpine-(?<version>\\d+\\.\\d+\\.\\d+)-\($spec.arch)-" +
109
"\($spec.firmware)-\($spec.bootstrap)\(if $spec.machine then "-" + $spec.machine else "" end)-" +
110
"(?<image_revision>r\\d+)\\.\($spec.file_extension)"
111
;"x"
112
) |
113
.version_number_array = ([.version | scan("\\d+") | tonumber])
114
] | sort_by(.version_number_array, .image_revision) | last
115
' <<<"${links_in_page}")
116
[[ -n ${latest_version_info} ]] || return
117
local newer_url_spec location sha512sum_location downloaded_sha256sum filename digest
118
# prefer the v<major>.<minor> in the path
119
newer_url_spec=$(jq -e -r ". + ${latest_version_info} | .path_version = \"v\" + (.version_number_array[:2]|map(tostring)|join(\".\"))" <<<"${url_spec}")
120
location=$(alpine_location_from_url_spec "${newer_url_spec}")
121
location=$(validate_url_without_redirect "${location}")
122
sha512sum_location="${location}.sha512"
123
downloaded_sha256sum=$(download_to_cache "${sha512sum_location}")
124
filename=$(alpine_image_filename_from_url_spec "${newer_url_spec}")
125
digest="sha512:$(<"${downloaded_sha256sum}")"
126
[[ -n ${digest} ]] || error_exit "Failed to get the digest for ${filename}"
127
json_vars location arch digest
128
}
129
130
function alpine_cache_key_for_image_kernel() {
131
local location=$1 url_spec
132
url_spec=$(alpine_url_spec_from_location "${location}")
133
jq -r '["alpine", .path_version, .target_vendor, .arch, .file_extension] | join(":")' <<<"${url_spec}"
134
}
135
136
function alpine_image_entry_for_image_kernel() {
137
local location=$1 kernel_is_not_supported=$2 overriding=${3:-'{"path_version":"latest-stable"}'} url_spec image_entry=''
138
[[ ${kernel_is_not_supported} == "null" ]] || echo "Updating kernel information is not supported on Alpine Linux" >&2
139
url_spec=$(alpine_url_spec_from_location "${location}" | jq -r ". + ${overriding}")
140
image_entry=$(alpine_latest_image_entry_for_url_spec "${url_spec}")
141
# shellcheck disable=SC2031
142
if [[ -z ${image_entry} ]]; then
143
error_exit "Failed to get the ${url_spec} image location for ${location}"
144
elif jq -e ".location == \"${location}\"" <<<"${image_entry}" >/dev/null; then
145
echo "Image location is up-to-date: ${location}" >&2
146
else
147
echo "${image_entry}"
148
fi
149
}
150
151
# check if the script is executed or sourced
152
# shellcheck disable=SC1091
153
if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
154
scriptdir=$(dirname "${BASH_SOURCE[0]}")
155
# shellcheck source=./cache-common-inc.sh
156
. "${scriptdir}/cache-common-inc.sh"
157
158
if ! command -v htmlq >/dev/null && ! command -v pup >/dev/null; then
159
error_exit "Please install 'htmlq' or 'pup' to list images from https://dl-cdn.alpinelinux.org/alpine/<version>/releases/cloud/"
160
fi
161
# shellcheck source=/dev/null # avoid shellcheck hangs on source looping
162
. "${scriptdir}/update-template.sh"
163
else
164
# this script is sourced
165
if ! command -v htmlq >/dev/null && ! command -v pup >/dev/null; then
166
echo "Please install 'htmlq' or 'pup' to list images from https://dl-cdn.alpinelinux.org/alpine/<version>/releases/cloud/" >&2
167
elif [[ -v SUPPORTED_DISTRIBUTIONS ]]; then
168
SUPPORTED_DISTRIBUTIONS+=("alpine")
169
else
170
declare -a SUPPORTED_DISTRIBUTIONS=("alpine")
171
fi
172
return 0
173
fi
174
175
declare -a templates=()
176
declare overriding='{}'
177
declare version_major='' version_minor=''
178
while [[ $# -gt 0 ]]; do
179
case "$1" in
180
-h | --help)
181
alpine_print_help
182
exit 0
183
;;
184
-d | --debug) set -x ;;
185
--version-major-minor)
186
if [[ -n ${2:-} && $2 != -* ]]; then
187
version="$2"
188
shift
189
else
190
error_exit "--version-major-minor requires a value"
191
fi
192
;&
193
--version-major-minor=*)
194
version=${version:-${1#*=}}
195
overriding=$(
196
version="${version#v}"
197
if [[ ${version} =~ ^v?[0-9]+.[0-9]+ ]]; then
198
version="$(echo "${version}" | cut -d. -f1-2)"
199
[[ ${version%%.*} -gt 3 || (${version%%.*} -eq 3 && ${version#*.} -ge 18) ]] || error_exit "Alpine Linux version must be 3.18 or later"
200
path_version="v${version}"
201
elif [[ ${version} == "latest-stable" ]]; then
202
path_version="latest-stable"
203
else
204
error_exit "--version-major-minor requires a value in the format <major>.<minor> or latest-stable"
205
fi
206
json_vars path_version <<<"${overriding}"
207
)
208
;;
209
--version-major)
210
if [[ -n ${2:-} && $2 != -* ]]; then
211
version_major="$2"
212
shift
213
else
214
error_exit "--version-major requires a value"
215
fi
216
;&
217
--version-major=*)
218
version_major=${version_major:-${1#*=}}
219
[[ ${version_major} =~ ^[0-9]+$ ]] || error_exit "Please specify --version-major in numbers"
220
;;
221
--version-minor)
222
if [[ -n ${2:-} && $2 != -* ]]; then
223
version_minor="$2"
224
shift
225
else
226
error_exit "--version-minor requires a value"
227
fi
228
;&
229
--version-minor=*)
230
version_minor=${version_minor:-${1#*=}}
231
[[ ${version_minor} =~ ^[0-9]+$ ]] || error_exit "Please specify --version-minor in numbers"
232
;;
233
*.yaml) templates+=("$1") ;;
234
*)
235
error_exit "Unknown argument: $1"
236
;;
237
esac
238
shift
239
[[ -z ${overriding} ]] && overriding="{}"
240
done
241
242
if ! jq -e '.path_version' <<<"${overriding}" >/dev/null; then # --version-major-minor is not specified
243
if [[ -n ${version_major} && -n ${version_minor} ]]; then
244
[[ ${version_major} -gt 3 || (${version_major} -eq 3 && ${version_minor} -ge 18) ]] || error_exit "Alpine Linux version must be 3.18 or later"
245
# shellcheck disable=2034
246
path_version="v${version_major}.${version_minor}"
247
overriding=$(json_vars path_version <<<"${overriding}")
248
elif [[ -n ${version_major} ]]; then
249
error_exit "--version-minor is required when --version-major is specified"
250
elif [[ -n ${version_minor} ]]; then
251
error_exit "--version-major is required when --version-minor is specified"
252
fi
253
elif [[ -n ${version_major} || -n ${version_minor} ]]; then # --version-major-minor is specified
254
echo "Ignoring --version-major and --version-minor because --version-major-minor is specified" >&2
255
fi
256
[[ ${overriding} == "{}" ]] && overriding='{"path_version":"latest-stable"}'
257
258
if [[ ${#templates[@]} -eq 0 ]]; then
259
alpine_print_help
260
exit 0
261
fi
262
263
declare -A image_entry_cache=()
264
265
for template in "${templates[@]}"; do
266
echo "Processing ${template}"
267
# 1. extract location by parsing template using arch
268
yq_filter="
269
.images[] | [.location, .kernel.location, .kernel.cmdline] | @tsv
270
"
271
parsed=$(yq eval "${yq_filter}" "${template}")
272
273
# 3. get the image location
274
arr=()
275
while IFS= read -r line; do arr+=("${line}"); done <<<"${parsed}"
276
locations=("${arr[@]}")
277
for ((index = 0; index < ${#locations[@]}; index++)); do
278
[[ ${locations[index]} != "null" ]] || continue
279
set -e
280
IFS=$'\t' read -r location kernel_location kernel_cmdline <<<"${locations[index]}"
281
set +e # Disable 'set -e' to avoid exiting on error for the next assignment.
282
cache_key=$(
283
set -e # Enable 'set -e' for the next command.
284
alpine_cache_key_for_image_kernel "${location}" "${kernel_location}"
285
) # Check exit status separately to prevent disabling 'set -e' by using the function call in the condition.
286
# shellcheck disable=2181
287
[[ $? -eq 0 ]] || continue
288
image_entry=$(
289
set -e # Enable 'set -e' for the next command.
290
if [[ -v image_entry_cache[${cache_key}] ]]; then
291
echo "${image_entry_cache[${cache_key}]}"
292
else
293
alpine_image_entry_for_image_kernel "${location}" "${kernel_location}" "${overriding}"
294
fi
295
) # Check exit status separately to prevent disabling 'set -e' by using the function call in the condition.
296
# shellcheck disable=2181
297
[[ $? -eq 0 ]] || continue
298
set -e
299
image_entry_cache[${cache_key}]="${image_entry}"
300
if [[ -n ${image_entry} ]]; then
301
[[ ${kernel_cmdline} != "null" ]] &&
302
jq -e 'has("kernel")' <<<"${image_entry}" >/dev/null &&
303
image_entry=$(jq ".kernel.cmdline = \"${kernel_cmdline}\"" <<<"${image_entry}")
304
echo "${image_entry}" | jq
305
limactl edit --log-level error --set "
306
.images[${index}] = ${image_entry}|
307
(.images[${index}] | ..) style = \"double\"
308
" "${template}"
309
fi
310
done
311
done
312
313