Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/hack/update-template-ubuntu.sh
1639 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 ubuntu_print_help() {
16
cat <<HELP
17
$(basename "${BASH_SOURCE[0]}"): Update the Ubuntu image location in the specified templates
18
19
Usage:
20
$(basename "${BASH_SOURCE[0]}") [--flavor <flavor>|--minimal|--server] [--version <version>] <template.yaml>...
21
22
Description:
23
This script updates the Ubuntu image location in the specified templates.
24
If the image location in the template contains a release date in the URL, the script replaces it with the latest available date.
25
If no flags are specified, the script uses the flavor and version from the image location basename in the template.
26
27
Image location basename format: ubuntu-<version>-<flavor>-cloudimg-<arch>.img
28
29
Released Ubuntu image information is fetched from the following URLs:
30
31
Server: https://cloud-images.ubuntu.com/releases/stream/v1/com.ubuntu.cloud:released:download.json
32
Minimal: https://cloud-images.ubuntu.com/minimal/releases/stream/v1/com.ubuntu.cloud:released:download.json
33
34
The downloaded JSON file will be cached in the Lima cache directory.
35
36
Examples:
37
Update the Ubuntu image location in templates/**.yaml:
38
$ $(basename "${BASH_SOURCE[0]}") templates/**.yaml
39
40
Update the Ubuntu image location in ~/.lima/ubuntu/lima.yaml:
41
$ $(basename "${BASH_SOURCE[0]}") ~/.lima/ubuntu/lima.yaml
42
$ limactl factory-reset ubuntu
43
44
Update the Ubuntu image location to ubuntu-24.04-minimal-cloudimg-<arch>.img in ~/.lima/docker/lima.yaml:
45
$ $(basename "${BASH_SOURCE[0]}") --minimal --version 24.04 ~/.lima/docker/lima.yaml
46
$ limactl factory-reset docker
47
48
Flags:
49
--flavor <flavor> Use the specified flavor image
50
--server Shortcut for --flavor server
51
--minimal Shortcut for --flavor minimal
52
--version <version> Use the specified version
53
The version can be an alias: latest, latest_lts, or lts.
54
-h, --help Print this help message
55
HELP
56
}
57
58
readonly -A ubuntu_base_urls=(
59
[minimal]=https://cloud-images.ubuntu.com/minimal/releases/
60
[server]=https://cloud-images.ubuntu.com/releases/
61
)
62
63
# ubuntu_base_url prints the base URL for the given flavor.
64
# e.g.
65
# ```console
66
# ubuntu_base_url minimal
67
# https://cloud-images.ubuntu.com/minimal/releases/
68
# ```
69
function ubuntu_base_url() {
70
[[ -v ubuntu_base_urls[$1] ]] || error_exit "Unsupported flavor: $1"
71
echo "${ubuntu_base_urls[$1]}"
72
}
73
74
# ubuntu_downloaded_json downloads the JSON file for the given flavor and prints the path.
75
# e.g.
76
# ```console
77
# ubuntu_downloaded_json server
78
# /Users/user/Library/Caches/lima/download/by-url-sha256/255f982f5bbda07f5377369093e21c506d7240f5ba901479bdadfa205ddafb01/data
79
# ```
80
function ubuntu_downloaded_json() {
81
local flavor=$1 base_url json_url
82
json_url=$(ubuntu_base_url "${flavor}")streams/v1/com.ubuntu.cloud:released:download.json
83
download_to_cache "${json_url}"
84
}
85
# ubuntu_image_url_try_replace_release_with_version tries to replace the release with the version in the URL.
86
# If the URL is valid, it prints the URL with the version.
87
function ubuntu_image_url_try_replace_release_with_version() {
88
local location=$1 release=$2 version=$3 location_using_version
89
set +e # Disable 'set -e' to avoid exiting on error for the next assignment.
90
location_using_version=$(
91
set -e
92
validate_url "${location/\/${release}\//\/${version}\/}" 2>/dev/null
93
) # Check exit status separately to prevent disabling 'set -e' by using the function call in the condition.
94
# shellcheck disable=2181
95
if [[ $? -eq 0 ]]; then
96
echo "${location_using_version}"
97
else
98
echo "${location}"
99
fi
100
set -e
101
}
102
103
# ubuntu_image_url_latest prints the latest image URL and its digest for the given flavor, version, arch, and path suffix.
104
function ubuntu_image_url_latest() {
105
local flavor=$1 version=$2 arch=$3 path_suffix=$4 base_url ubuntu_downloaded_json jq_filter location_digest_release
106
base_url=$(ubuntu_base_url "${flavor}")
107
ubuntu_downloaded_json=$(ubuntu_downloaded_json "${flavor}")
108
jq_filter="
109
[
110
.products[\"com.ubuntu.cloud:${flavor}:${version}:${arch}\"] |
111
.release as \$release |
112
.versions[]?.items[] | select(.path | endswith(\"${path_suffix}\")) |
113
[\"${base_url}\"+.path, \"sha256:\"+.sha256, \$release] | @tsv
114
] | last
115
"
116
location_digest_release=$(jq -r "${jq_filter}" "${ubuntu_downloaded_json}")
117
[[ ${location_digest_release} != "null" ]] ||
118
error_exit "The URL for ubuntu-${version}-${flavor}-cloudimg-${arch}${path_suffix} is not provided at ${ubuntu_base_urls[${flavor}]}."
119
local location digest release location_using_version
120
read -r location digest release <<<"${location_digest_release}"
121
location=$(validate_url "${location}")
122
location=$(ubuntu_image_url_try_replace_release_with_version "${location}" "${release}" "${version}")
123
arch=$(limayaml_arch "${arch}")
124
json_vars location arch digest
125
}
126
127
# ubuntu_image_url_release prints the release image URL for the given flavor, version, arch, and path suffix.
128
function ubuntu_image_url_release() {
129
local flavor=$1 version=$2 arch=$3 path_suffix=$4 base_url
130
base_url=$(ubuntu_base_url "${flavor}")
131
ubuntu_downloaded_json=$(ubuntu_downloaded_json "${flavor}")
132
local jq_filter release location
133
jq_filter="
134
[
135
.products | to_entries[] as \$product_entry |
136
\$product_entry.value| select(.version == \"${version}\") |
137
.release
138
] | first
139
"
140
release=$(jq -r "${jq_filter}" "${ubuntu_downloaded_json}")
141
[[ ${release} != "null" ]] ||
142
error_exit "The URL for ubuntu-${version}-${flavor}-cloudimg-${arch}${path_suffix} is not provided at ${ubuntu_base_urls[${flavor}]}."
143
location=$(validate_url "${base_url}${release}/release/ubuntu-${version}-${flavor}-cloudimg-${arch}${path_suffix}")
144
location=$(ubuntu_image_url_try_replace_release_with_version "${location}" "${release}" "${version}")
145
arch=$(limayaml_arch "${arch}")
146
json_vars location arch
147
}
148
149
function ubuntu_file_info() {
150
local location=$1 location_dirname sha256sums location_basename digest
151
location=$(validate_url "${location}")
152
location_dirname=$(dirname "${location}")
153
sha256sums=$(download_to_cache "${location_dirname}/SHA256SUMS")
154
location_basename=$(basename "${location}")
155
# shellcheck disable=SC2034
156
digest=${location+$(awk "/${location_basename}/{print \"sha256:\"\$1}" "${sha256sums}")}
157
json_vars location digest
158
}
159
160
# ubuntu_image_entry_with_kernel_info prints image entry with kernel and initrd info.
161
# $1: image_entry
162
# e.g.
163
# ```console
164
# ubuntu_image_entry_with_kernel_info '{"location":"https://cloud-images.ubuntu.com/minimal/releases/24.04/release-20210914/ubuntu-24.04-minimal-cloudimg-amd64.img"}'
165
# {"location":"https://cloud-images.ubuntu.com/minimal/releases/24.04/release-20210914/ubuntu-24.04-minimal-cloudimg-amd64.img","kernel":{"location":"https://cloud-images.ubuntu.com/minimal/releases/24.04/release-20210914/ubuntu-24.04-minimal-cloudimg-vmlinuz-generic","digest":"sha256:..."}}
166
# ```
167
# shellcheck disable=SC2034
168
function ubuntu_image_entry_with_kernel_info() {
169
local image_entry=$1 location
170
location=$(jq -e -r '.location' <<<"${image_entry}")
171
local location_dirname location_basename location_prefix
172
location_dirname=$(dirname "${location}")/unpacked
173
location_basename="$(basename "${location}" | cut -d- -f1-5 | cut -d. -f1-2)"
174
location_prefix="${location_dirname}/${location_basename}"
175
local kernel initrd
176
set +e # Disable 'set -e' to avoid exiting on error for the next assignment.
177
kernel=$(
178
set -e
179
ubuntu_file_info "${location_prefix}-vmlinuz-generic"
180
) # Check exit status separately to prevent disabling 'set -e' by using the function call in the condition.
181
# shellcheck disable=2181
182
[[ $? -eq 0 ]] || error_exit "kernel image not found at ${location_prefix}-vmlinuz-generic"
183
initrd=$(
184
set -e
185
ubuntu_file_info "${location_prefix}-initrd-generic" 2>/dev/null
186
) # may not exist
187
set -e
188
json_vars kernel initrd <<<"${image_entry}"
189
}
190
191
function ubuntu_flavor_from_location_basename() {
192
local location=$1 location_basename flavor
193
location_basename=$(basename "${location}")
194
flavor=$(echo "${location_basename}" | cut -d- -f3)
195
[[ -n ${flavor} ]] || error_exit "Failed to get flavor from ${location}"
196
echo "${flavor}"
197
}
198
199
function ubuntu_version_from_location_basename() {
200
local location=$1 location_basename version
201
location_basename=$(basename "${location}")
202
version=$(echo "${location_basename}" | cut -d- -f2)
203
[[ -n ${version} ]] || error_exit "Failed to get version from ${location}"
204
echo "${version}"
205
}
206
207
# ubuntu_version_latest_lts prints the latest LTS version for the given flavor.
208
# e.g.
209
# ```console
210
# ubuntu_version_latest_lts minimal
211
# 24.04
212
# ```
213
function ubuntu_version_latest_lts() {
214
local flavor=${1:-server}
215
ubuntu_downloaded_json=$(ubuntu_downloaded_json "${flavor}")
216
jq -e -r '[.products[]|.version|select(endswith(".04"))]|last // empty' "${ubuntu_downloaded_json}"
217
}
218
219
# ubuntu_version_latest prints the latest version for the given flavor.
220
# e.g.
221
# ```console
222
# ubuntu_version_latest minimal
223
# 24.10
224
# ```
225
function ubuntu_version_latest() {
226
local flavor=${1:-server}
227
ubuntu_downloaded_json=$(ubuntu_downloaded_json "${flavor}")
228
jq -e -r '[.products[]|.version]|last // empty' "${ubuntu_downloaded_json}"
229
}
230
231
# ubuntu_version_resolve_aliases resolves the version aliases.
232
# e.g.
233
# ```console
234
# ubuntu_version_resolve_aliases https://cloud-images.ubuntu.com/minimal/releases/24.04/release-20210914/ubuntu-24.04-minimal-cloudimg-amd64.img minimal latest
235
# 24.10
236
# ubuntu_version_resolve_aliases https://cloud-images.ubuntu.com/minimal/releases/24.04/release-20210914/ubuntu-24.04-minimal-cloudimg-amd64.img minimal latest_lts
237
# 24.04
238
# ubuntu_version_resolve_aliases https://cloud-images.ubuntu.com/minimal/releases/24.04/release-20210914/ubuntu-24.04-minimal-cloudimg-amd64.img
239
#
240
# ```
241
function ubuntu_version_resolve_aliases() {
242
local location=$1 flavor version
243
flavor=${2:-$(ubuntu_flavor_from_location_basename "${location}")}
244
version=${3:-}
245
case "${version}" in
246
latest_lts | lts) ubuntu_version_latest_lts "${flavor}" ;;
247
latest) ubuntu_version_latest "${flavor}" ;;
248
*) echo "${version}" ;;
249
esac
250
}
251
252
function ubuntu_arch_from_location_basename() {
253
local location=$1 location_basename arch
254
location_basename=$(basename "${location}")
255
arch=$(echo "${location_basename}" | cut -d- -f5 | cut -d. -f1)
256
[[ -n ${arch} ]] || error_exit "Failed to get arch from ${location}"
257
echo "${arch}"
258
}
259
260
function ubuntu_path_suffix_from_location_basename() {
261
local location=$1 arch path_suffix
262
arch=$(ubuntu_arch_from_location_basename "${location}")
263
path_suffix="${location##*"${arch}"}"
264
[[ -n ${path_suffix} ]] || error_exit "Failed to get path suffix from ${location}"
265
echo "${path_suffix}"
266
}
267
268
# ubuntu_location_url_spec prints the URL spec for the given location.
269
# If the location is not supported, it returns 1.
270
# e.g.
271
# ```console
272
# ubuntu_location_url_spec https://cloud-images.ubuntu.com/minimal/releases/24.04/release-20210914/ubuntu-24.04-minimal-cloudimg-amd64.img
273
# latest
274
# ubuntu_location_url_spec https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img
275
# release
276
# ```
277
function ubuntu_location_url_spec() {
278
local location=$1 url_spec
279
case "${location}" in
280
https://cloud-images.ubuntu.com/minimal/releases/*/release/*) url_spec=release ;;
281
https://cloud-images.ubuntu.com/minimal/releases/*/release-*/*) url_spec=latest ;;
282
https://cloud-images.ubuntu.com/releases/*/release/*) url_spec=release ;;
283
https://cloud-images.ubuntu.com/releases/*/release-*/*) url_spec=latest ;;
284
*)
285
# echo "Unsupported image location: ${location}" >&2
286
return 1
287
;;
288
esac
289
echo "${url_spec}"
290
}
291
292
# ubuntu_cache_key_for_image_kernel_flavor_version prints the cache key for the given location, kernel_location, flavor, and version.
293
# If the image location is not supported, it returns 1.
294
# kernel_location is not validated.
295
# e.g.
296
# ```console
297
# ubuntu_cache_key_for_image_kernel_flavor_version https://cloud-images.ubuntu.com/minimal/releases/24.04/release-20210914/ubuntu-24.04-minimal-cloudimg-amd64.img
298
# ubuntu_latest_24.04-minimal-amd64-release-.img
299
# ubuntu_cache_key_for_image_kernel_flavor_version https://cloud-images.ubuntu.com/minimal/releases/24.04/release-20210914/ubuntu-24.04-minimal-cloudimg-amd64.img https://...
300
# ubuntu_latest_with_kernel_24.04-minimal-amd64-release-.img
301
# ubuntu_cache_key_for_image_kernel_flavor_version https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img null
302
# ubuntu_release_24.04-server-amd64-.img
303
# ```
304
function ubuntu_cache_key_for_image_kernel_flavor_version() {
305
local location=$1 kernel_location=${2:-null} url_spec with_kernel='' flavor version arch path_suffix
306
url_spec=$(ubuntu_location_url_spec "${location}")
307
[[ ${kernel_location} != "null" ]] && with_kernel=_with_kernel
308
flavor=$(ubuntu_flavor_from_location_basename "${location}")
309
version=$(ubuntu_version_from_location_basename "${location}")
310
arch=$(ubuntu_arch_from_location_basename "${location}")
311
path_suffix=$(ubuntu_path_suffix_from_location_basename "${location}")
312
echo "ubuntu_${url_spec}${with_kernel}_${version}-${flavor}-${arch}-${path_suffix}"
313
}
314
315
function ubuntu_image_entry_for_image_kernel_flavor_version() {
316
local location=$1 kernel_location=$2 url_spec
317
url_spec=$(ubuntu_location_url_spec "${location}")
318
319
local flavor version arch path_suffix
320
flavor=${3:-$(ubuntu_flavor_from_location_basename "${location}")}
321
version=${4:-$(ubuntu_version_from_location_basename "${location}")}
322
arch=$(ubuntu_arch_from_location_basename "${location}")
323
path_suffix=$(ubuntu_path_suffix_from_location_basename "${location}")
324
325
local image_entry
326
image_entry=$(ubuntu_image_url_"${url_spec}" "${flavor}" "${version}" "${arch}" "${path_suffix}")
327
if [[ -z ${image_entry} ]]; then
328
error_exit "Failed to get the ${url_spec} image location for ${location}"
329
elif jq -e ".location == \"${location}\"" <<<"${image_entry}" >/dev/null; then
330
echo "Image location is up-to-date: ${location}" >&2
331
elif [[ ${kernel_location} != "null" ]]; then
332
ubuntu_image_entry_with_kernel_info "${image_entry}"
333
else
334
echo "${image_entry}"
335
fi
336
}
337
338
# check if the script is executed or sourced
339
# shellcheck disable=SC1091
340
if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
341
scriptdir=$(dirname "${BASH_SOURCE[0]}")
342
# shellcheck source=./cache-common-inc.sh
343
. "${scriptdir}/cache-common-inc.sh"
344
345
# shellcheck source=/dev/null # avoid shellcheck hangs on source looping
346
. "${scriptdir}/update-template.sh"
347
else
348
# this script is sourced
349
if [[ -v SUPPORTED_DISTRIBUTIONS ]]; then
350
SUPPORTED_DISTRIBUTIONS+=("ubuntu")
351
else
352
declare -a SUPPORTED_DISTRIBUTIONS=("ubuntu")
353
fi
354
# required functions for Ubuntu
355
function ubuntu_cache_key_for_image_kernel() { ubuntu_cache_key_for_image_kernel_flavor_version "$@"; }
356
function ubuntu_image_entry_for_image_kernel() { ubuntu_image_entry_for_image_kernel_flavor_version "$@"; }
357
358
return 0
359
fi
360
361
declare -a templates=()
362
declare overriding_flavor=
363
declare overriding_version=
364
while [[ $# -gt 0 ]]; do
365
case "$1" in
366
-h | --help)
367
ubuntu_print_help
368
exit 0
369
;;
370
-d | --debug) set -x ;;
371
--flavor)
372
if [[ -n $2 && $2 != -* ]]; then
373
overriding_flavor="$2"
374
shift
375
else
376
error_exit "--flavor requires a value"
377
fi
378
;;
379
--flavor=*) overriding_flavor="${1#*=}" ;;
380
--minimal) overriding_flavor="minimal" ;;
381
--server) overriding_flavor="server" ;;
382
--version)
383
if [[ -n $2 && $2 != -* ]]; then
384
overriding_version="$2"
385
shift
386
else
387
error_exit "--version requires a value"
388
fi
389
;;
390
--version=*) overriding_version="${1#*=}" ;;
391
*.yaml) templates+=("$1") ;;
392
*)
393
error_exit "Unknown argument: $1"
394
;;
395
esac
396
shift
397
done
398
399
if [[ ${#templates[@]} -eq 0 ]]; then
400
ubuntu_print_help
401
exit 0
402
fi
403
404
declare -A image_entry_cache=()
405
406
for template in "${templates[@]}"; do
407
echo "Processing ${template}"
408
# 1. extract location by parsing template using arch
409
yq_filter="
410
.images[] | [.location, .kernel.location, .kernel.cmdline] | @tsv
411
"
412
parsed=$(yq eval "${yq_filter}" "${template}")
413
414
# 3. get the image location
415
arr=()
416
while IFS= read -r line; do arr+=("${line}"); done <<<"${parsed}"
417
locations=("${arr[@]}")
418
for ((index = 0; index < ${#locations[@]}; index++)); do
419
[[ ${locations[index]} != "null" ]] || continue
420
set -e
421
IFS=$'\t' read -r location kernel_location kernel_cmdline <<<"${locations[index]}"
422
set +e # Disable 'set -e' to avoid exiting on error for the next assignment.
423
overriding_version=$(
424
set -e # Enable 'set -e' for the next command.
425
ubuntu_version_resolve_aliases "${location}" "${overriding_flavor}" "${overriding_version}"
426
) # Check exit status separately to prevent disabling 'set -e' by using the function call in the condition.
427
# shellcheck disable=2181
428
[[ $? -eq 0 ]] || continue
429
cache_key=$(
430
set -e # Enable 'set -e' for the next command.
431
ubuntu_cache_key_for_image_kernel_flavor_version "${location}" "${kernel_location}"
432
) # Check exit status separately to prevent disabling 'set -e' by using the function call in the condition.
433
# shellcheck disable=2181
434
[[ $? -eq 0 ]] || continue
435
image_entry=$(
436
set -e # Enable 'set -e' for the next command.
437
if [[ -v image_entry_cache[${cache_key}] ]]; then
438
echo "${image_entry_cache[${cache_key}]}"
439
else
440
ubuntu_image_entry_for_image_kernel_flavor_version "${location}" "${kernel_location}" "${overriding_flavor}" "${overriding_version}"
441
fi
442
) # Check exit status separately to prevent disabling 'set -e' by using the function call in the condition.
443
# shellcheck disable=2181
444
[[ $? -eq 0 ]] || continue
445
set -e
446
image_entry_cache[${cache_key}]="${image_entry}"
447
if [[ -n ${image_entry} ]]; then
448
[[ ${kernel_cmdline} != "null" ]] &&
449
jq -e 'has("kernel")' <<<"${image_entry}" >/dev/null &&
450
image_entry=$(jq ".kernel.cmdline = \"${kernel_cmdline}\"" <<<"${image_entry}")
451
echo "${image_entry}" | jq
452
limactl edit --log-level error --set "
453
.images[${index}] = ${image_entry}|
454
(.images[${index}] | ..) style = \"double\"
455
" "${template}"
456
fi
457
done
458
done
459
460