Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/hack/cache-common-inc.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
# print the error message and exit with status 1
7
function error_exit() {
8
echo "Error: $*" >&2
9
exit 1
10
}
11
12
# e.g.
13
# ```console
14
# $ download_template_if_needed templates/default.yaml
15
# templates/default.yaml
16
# $ download_template_if_needed https://raw.githubusercontent.com/lima-vm/lima/v0.15.1/examples/ubuntu-lts.yaml
17
# /tmp/tmp.1J9Q6Q/template.yaml
18
# ```
19
function download_template_if_needed() {
20
local template="$1"
21
tmp_yaml="$(mktemp -d)/template.yaml"
22
# The upgrade test doesn't have limactl installed first. The old version wouldn't support `limactl tmpl` anyways.
23
if command -v limactl >/dev/null; then
24
limactl tmpl copy --embed-all "${template}" "${tmp_yaml}" || return
25
else
26
if [[ $template == https://* ]]; then
27
curl -sSLf "${template}" >"${tmp_yaml}" || return
28
else
29
cp "${template}" "${tmp_yaml}"
30
fi
31
fi
32
echo "${tmp_yaml}"
33
}
34
35
# e.g.
36
# ```console
37
# $ print_image_locations_for_arch_from_template templates/default.yaml
38
# https://cloud-images.ubuntu.com/releases/24.04/release-20240809/ubuntu-24.04-server-cloudimg-arm64.img sha256:2e0c90562af1970ffff220a5073a7830f4acc2aad55b31593003e8c363381e7a
39
# https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-arm64.img null
40
# ```
41
function print_image_locations_for_arch_from_template() {
42
local template arch
43
template=$(download_template_if_needed "$1") || return
44
local -r template=${template}
45
arch=$(detect_arch "${template}" "${2:-}") || return
46
local -r arch=${arch}
47
48
# extract digest, location and size by parsing template using arch
49
local -r yq_filter="[.images | map(select(.arch == \"${arch}\")) | .[].location] | .[]"
50
yq eval "${yq_filter}" "${template}"
51
}
52
53
# e.g.
54
# ```console
55
# $ detect_arch templates/default.yaml
56
# x86_64
57
# $ detect_arch templates/default.yaml arm64
58
# aarch64
59
# ```
60
function detect_arch() {
61
local template arch
62
template=$(download_template_if_needed "$1") || return
63
local -r template=${template}
64
65
arch="${2:-$(yq '.arch // ""' "${template}")}"
66
arch="${arch:-$(uname -m)}"
67
# normalize arch. amd64 -> x86_64, arm64 -> aarch64
68
case "${arch}" in
69
amd64 | x86_64) arch=x86_64 ;;
70
aarch64 | arm64) arch=aarch64 ;;
71
*) ;;
72
esac
73
echo "${arch}"
74
}
75
76
# e.g.
77
# ```console
78
# $ print_image_locations_for_arch_from_template templates/default.yaml|print_valid_image_index
79
# 0
80
# ```
81
function print_valid_image_index() {
82
local index=0
83
while read -r location; do
84
[[ ${location} != "null" ]] || continue
85
http_code_and_size=$(check_location_with_cache "${location}")
86
read -r http_code _size <<<"${http_code_and_size}"
87
if [[ ${http_code} -eq 200 ]]; then
88
echo "${index}"
89
return
90
fi
91
index=$((index + 1))
92
done
93
echo "Failed to get the valid image location" >&2
94
return 1
95
}
96
97
# e.g.
98
# ```console
99
# $ size_from_location "https://cloud-images.ubuntu.com/releases/24.04/release-20240725/ubuntu-24.04-server-cloudimg-amd64.img"
100
# 585498624
101
# ```
102
function size_from_location() {
103
(
104
set -o pipefail
105
local location=$1
106
check_location "${location}" | cut -d' ' -f2
107
)
108
}
109
110
# Check the remote location and print the http code and size.
111
# If GITHUB_ACTIONS is true, the result is not cached.
112
# e.g.
113
# ```console
114
# $ check_location "https://cloud-images.ubuntu.com/releases/24.04/release-20240725/ubuntu-24.04-server-cloudimg-amd64.img"
115
# 200 585498624
116
# ```
117
function check_location() {
118
# shellcheck disable=SC2154
119
if [[ ${GITHUB_ACTIONS:-false} == true ]]; then
120
check_location_without_cache "$1"
121
else
122
check_location_with_cache "$1"
123
fi
124
}
125
126
# Check the remote location and print the http code and size.
127
# The result is cached in .check_location-response-cache.yaml
128
# e.g.
129
# ```console
130
# $ check_location_with_cache "https://cloud-images.ubuntu.com/releases/24.04/release-20240725/ubuntu-24.04-server-cloudimg-amd64.img"
131
# 200 585498624
132
# ```
133
function check_location_with_cache() {
134
local -r location="$1" cache_file=".check_location-response-cache.yaml"
135
# check ${cache_file} for the cache
136
if [[ -f ${cache_file} ]]; then
137
cached=$(yq -e eval ".[\"${location}\"]" "${cache_file}" 2>/dev/null) && echo "${cached}" && return
138
else
139
touch "${cache_file}"
140
fi
141
http_code_and_size=$(check_location_without_cache "${location}") || return
142
yq eval ".[\"${location}\"] = \"${http_code_and_size}\"" -i "${cache_file}" || return
143
echo "${http_code_and_size}"
144
}
145
146
# e.g.
147
# ```console
148
# $ check_location "https://cloud-images.ubuntu.com/releases/24.04/release-20240725/ubuntu-24.04-server-cloudimg-amd64.img"
149
# 200 585498624
150
# ```
151
function check_location_without_cache() {
152
local -r location="$1"
153
curl -sIL -w "%{http_code} %header{Content-Length}" "${location}" -o /dev/null
154
}
155
156
# e.g.
157
# ```console
158
# $ print_image_kernel_initrd_locations_with_digest_for_arch_from_template_at_index templates/default.yaml 0
159
# https://cloud-images.ubuntu.com/releases/24.04/release-20240809/ubuntu-24.04-server-cloudimg-arm64.img
160
# sha256:2e0c90562af1970ffff220a5073a7830f4acc2aad55b31593003e8c363381e7a
161
# null
162
# null
163
# null
164
# null
165
# ```
166
function print_image_kernel_initrd_locations_with_digest_for_arch_from_template_at_index() {
167
local template index="${2:-}" arch
168
template=$(download_template_if_needed "$1") || return
169
local -r template=${template}
170
arch=$(detect_arch "${template}" "${3:-}") || return
171
local -r arch=${arch}
172
173
local -r yq_filter="[(.images[] | select(.arch == \"${arch}\"))].[${index}]|[
174
.location,
175
.digest,
176
.kernel.location,
177
.kernel.digest,
178
.initrd.location,
179
.initrd.digest
180
]"
181
yq -o=t eval "${yq_filter}" "${template}"
182
}
183
184
# e.g.
185
# ```console
186
# $ print_containerd_config_for_arch_from_template templates/default.yaml
187
# true
188
# https://github.com/containerd/nerdctl/releases/download/v1.7.6/nerdctl-full-1.7.6-linux-arm64.tar.gz
189
# sha256:77c747f09853ee3d229d77e8de0dd3c85622537d82be57433dc1fca4493bab95
190
# ```
191
function print_containerd_config_for_arch_from_template() {
192
local template arch
193
template=$(download_template_if_needed "$1") || return
194
local -r template=${template}
195
arch=$(detect_arch "${template}" "${2:-}") || return
196
local -r arch=${arch}
197
198
local -r yq_filter="
199
[.containerd|[.system or .user],
200
.containerd.archives | map(select(.arch == \"${arch}\")) | [.[0].location, .[0].digest]]|flatten
201
"
202
validated_template="$(
203
limactl validate "${template}" --fill 2>/dev/null || echo "{.containerd: {system: false, user: false, archives: []}}"
204
)"
205
yq -o=t eval "${yq_filter}" <<<"${validated_template}"
206
}
207
208
# e.g.
209
# ```console
210
# $ location_to_sha256 "https://cloud-images.ubuntu.com/releases/24.04/release-20240809/ubuntu-24.04-server-cloudimg-arm64.img"
211
# ae988d797c6de06b9c8a81a2b814904151135ccfd4616c22948057f6280477e8
212
# ```
213
function location_to_sha256() {
214
(
215
set -o pipefail
216
local -r location="$1"
217
if command -v sha256sum >/dev/null; then
218
sha256="$(echo -n "${location}" | sha256sum | cut -d' ' -f1)"
219
elif command -v shasum >/dev/null; then
220
sha256="$(echo -n "${location}" | shasum -a 256 | cut -d' ' -f1)"
221
else
222
error_exit "sha256sum or shasum not found"
223
fi
224
echo "${sha256}"
225
)
226
}
227
228
# e.g.
229
# ```console
230
# $ cache_download_dir
231
# .download # on GitHub Actions
232
# /home/user/.cache/lima/download # on Linux
233
# /Users/user/Library/Caches/lima/download # on macOS
234
# /home/user/.cache/lima/download # on others
235
# ```
236
function cache_download_dir() {
237
if [[ ${GITHUB_ACTIONS:-false} == true ]]; then
238
echo ".download"
239
else
240
case "$(uname -s)" in
241
Linux) echo "${XDG_CACHE_HOME:-${HOME}/.cache}/lima/download" ;;
242
Darwin) echo "${HOME}/Library/Caches/lima/download" ;;
243
*) echo "${HOME}/.cache/lima/download" ;;
244
esac
245
fi
246
}
247
248
# e.g.
249
# ```console
250
# $ location_to_cache_path "https://cloud-images.ubuntu.com/releases/24.04/release-20240809/ubuntu-24.04-server-cloudimg-arm64.img"
251
# .download/by-url-sha256/ae988d797c6de06b9c8a81a2b814904151135ccfd4616c22948057f6280477e8
252
# ```
253
function location_to_cache_path() {
254
local location=$1
255
[[ ${location} != "null" ]] || return
256
sha256=$(location_to_sha256 "${location}") && download_dir=$(cache_download_dir) && echo "${download_dir}/by-url-sha256/${sha256}"
257
}
258
259
# e.g.
260
# ```console
261
# $ 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"
262
# image:ubuntu-24.04-server-cloudimg-arm64.img-sha256:2e0c90562af1970ffff220a5073a7830f4acc2aad55b31593003e8c363381e7a
263
# $ 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"
264
# image:ubuntu-24.04-server-cloudimg-arm64.img-url-sha256:ae988d797c6de06b9c8a81a2b814904151135ccfd4616c22948057f6280477e8
265
# ```
266
function cache_key_from_prefix_location_and_digest() {
267
local prefix=$1 location=$2 digest=$3 location_basename
268
[[ ${location} != "null" ]] || return
269
location_basename=$(basename "${location}")
270
if [[ ${digest} != "null" ]]; then
271
echo "${prefix}:${location_basename}-${digest}"
272
else
273
# use sha256 of location as key if digest is not available
274
echo "${prefix}:${location_basename}-url-sha256:$(location_to_sha256 "${location}")"
275
fi
276
}
277
278
# e.g.
279
# ```console
280
# $ 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"
281
# image-path=.download/by-url-sha256/ae988d797c6de06b9c8a81a2b814904151135ccfd4616c22948057f6280477e8
282
# image-key=image:ubuntu-24.04-server-cloudimg-arm64.img-sha256:2e0c90562af1970ffff220a5073a7830f4acc2aad55b31593003e8c363381e7a
283
# ```
284
function print_path_and_key_for_cache() {
285
local -r prefix=$1 location=$2 digest=$3
286
cache_path=$(location_to_cache_path "${location}" || true)
287
cache_key=$(cache_key_from_prefix_location_and_digest "${prefix}" "${location}" "${digest}" || true)
288
echo "${prefix}-path=${cache_path}"
289
echo "${prefix}-key=${cache_key}"
290
}
291
292
# e.g.
293
# ```console
294
# $ print_cache_informations_from_template templates/default.yaml
295
# image-path=.download/by-url-sha256/ae988d797c6de06b9c8a81a2b814904151135ccfd4616c22948057f6280477e8
296
# image-key=image:ubuntu-24.04-server-cloudimg-arm64.img-sha256:2e0c90562af1970ffff220a5073a7830f4acc2aad55b31593003e8c363381e7a
297
# kernel-path=
298
# kernel-key=
299
# initrd-path=
300
# initrd-key=
301
# containerd-path=.download/by-url-sha256/21cc8dfa548ea8a678135bd6984c9feb9f8a01901d10b11bb491f6f4e7537158
302
# containerd-key=containerd:nerdctl-full-1.7.6-linux-arm64.tar.gz-sha256:77c747f09853ee3d229d77e8de0dd3c85622537d82be57433dc1fca4493bab95
303
# $ print_cache_informations_from_template templates/experimental/riscv64.yaml
304
# image-path=.download/by-url-sha256/760b6ec69c801177bdaea06d7ee25bcd6ab72a331b9d3bf38376578164eb8f01
305
# image-key=image:ubuntu-24.04-server-cloudimg-riscv64.img-sha256:361d72c5ed9781b097ab2dfb1a489c64e51936be648bbc5badee762ebdc50c31
306
# kernel-path=.download/by-url-sha256/4568026693dc0f31a551b6741839979c607ee6bb0bf7209c89f3348321c52c61
307
# kernel-key=kernel:qemu-riscv64_smode_uboot.elf-sha256:d4b3a10c3ef04219641802a586dca905e768805f5a5164fb68520887df54f33c
308
# initrd-path=
309
# initrd-key=
310
# ```
311
function print_cache_informations_from_template() {
312
(
313
set -o pipefail
314
local template index image_kernel_initrd_info location digest containerd_info
315
template=$(download_template_if_needed "$1") || return
316
local -r template="${template}"
317
index=$(print_image_locations_for_arch_from_template "${template}" "${@:2}" | print_valid_image_index) || return
318
local -r index="${index}"
319
image_kernel_initrd_info=$(print_image_kernel_initrd_locations_with_digest_for_arch_from_template_at_index "${template}" "${index}" "${@:2}") || return
320
# shellcheck disable=SC2034
321
read -r image_location image_digest kernel_location kernel_digest initrd_location initrd_digest <<<"${image_kernel_initrd_info}"
322
for prefix in image kernel initrd; do
323
location=${prefix}_location
324
digest=${prefix}_digest
325
print_path_and_key_for_cache "${prefix}" "${!location}" "${!digest}"
326
done
327
if command -v limactl >/dev/null; then
328
containerd_info=$(print_containerd_config_for_arch_from_template "${template}" "${@:2}") || return
329
read -r containerd_enabled containerd_location containerd_digest <<<"${containerd_info}"
330
if [[ ${containerd_enabled} == "true" ]]; then
331
print_path_and_key_for_cache "containerd" "${containerd_location}" "${containerd_digest}"
332
fi
333
fi
334
)
335
}
336
337
# Compatible with hashFile() in GitHub Actions
338
# e.g.
339
# ```console
340
# $ hash_file templates/default.yaml
341
# ceec5ba3dc8872c083b2eb7f44e3e3f295d5dcdeccf0961ee153be6586525e5e
342
# ```
343
function hash_file() {
344
(
345
set -o pipefail
346
local hash=""
347
for file in "$@"; do
348
hash="${hash}$(sha256sum "${file}" | cut -d' ' -f1)" || return
349
done
350
echo "${hash}" | xxd -r -p | sha256sum | cut -d' ' -f1
351
)
352
}
353
354
# Download the file to the cache directory and print the path.
355
# e.g.
356
# ```console
357
# $ download_to_cache "https://cloud-images.ubuntu.com/releases/24.04/release-20240821/ubuntu-24.04-server-cloudimg-arm64.img"
358
# .download/by-url-sha256/346ee1ff9e381b78ba08e2a29445960b5cd31c51f896fc346b82e26e345a5b9a/data # on GitHub Actions
359
# /home/user/.cache/lima/download/by-url-sha256/346ee1ff9e381b78ba08e2a29445960b5cd31c51f896fc346b82e26e345a5b9a/data # on Linux
360
# /Users/user/Library/Caches/lima/download/by-url-sha256/346ee1ff9e381b78ba08e2a29445960b5cd31c51f896fc346b82e26e345a5b9a/data # on macOS
361
# /home/user/.cache/lima/download/by-url-sha256/346ee1ff9e381b78ba08e2a29445960b5cd31c51f896fc346b82e26e345a5b9a/data # on others
362
function download_to_cache() {
363
local cache_path use_redirected_location=${2:-YES}
364
cache_path=$(location_to_cache_path "$1")
365
# before checking remote location, check if the data file is already downloaded and the time file is updated within 10 minutes
366
if [[ -f ${cache_path}/data && -n "$(find "${cache_path}/time" -mmin -10 || true)" ]]; then
367
echo "${cache_path}/data"
368
return
369
fi
370
371
# check the remote location
372
local curl_info_json write_out
373
write_out='{
374
"json":%{json},
375
"header":%{header_json}
376
}'
377
curl_info_json=$(curl -sSLI -w "${write_out}" "$1" -o /dev/null)
378
379
local code time type url
380
code=$(jq -r '.json.http_code' <<<"${curl_info_json}")
381
time=$(jq -r '(.header["last-modified"]|first) // (.header["date"]|first) // empty' <<<"${curl_info_json}")
382
type=$(jq -r '.json.content_type' <<<"${curl_info_json}")
383
if [[ ${use_redirected_location} == "YES" ]]; then
384
url=$(jq -r '.json.url_effective' <<<"${curl_info_json}")
385
else
386
url=$1
387
fi
388
[[ ${code} == 200 ]] || error_exit "Failed to download $1"
389
390
cache_path=$(location_to_cache_path "${url}")
391
[[ -d ${cache_path} ]] || mkdir -p "${cache_path}"
392
393
local needs_download=0
394
[[ -f ${cache_path}/data ]] || needs_download=1
395
[[ -f ${cache_path}/time && "$(<"${cache_path}/time")" == "${time}" ]] || needs_download=1
396
[[ -f ${cache_path}/type && "$(<"${cache_path}/type")" == "${type}" ]] || needs_download=1
397
if [[ ${needs_download} -eq 1 ]]; then
398
curl_info_json=$(
399
echo "downloading ${url}" >&2
400
curl -SL -w "${write_out}" --no-clobber -o "${cache_path}/data" "${url}"
401
)
402
local filename
403
code=$(jq -r '.json.http_code' <<<"${curl_info_json}")
404
time=$(jq -r '(.header["last-modified"]|first) // (.header["date"]|first) // empty' <<<"${curl_info_json}")
405
type=$(jq -r '.json.content_type' <<<"${curl_info_json}")
406
url=$(jq -r '.json.url_effective' <<<"${curl_info_json}")
407
filename=$(jq -r '.json.filename_effective' <<<"${curl_info_json}")
408
[[ ${code} == 200 ]] || error_exit "Failed to download ${url}"
409
[[ "${cache_path}/data" == "${filename}" ]] || mv "${filename}" "${cache_path}/data"
410
# sha256.digest seems existing if expected digest is available. so, not creating it here.
411
# sha256sum "${cache_path}/data" | awk '{print "sha256:"$1}' >"${cache_path}/sha256.digest"
412
echo -n "${time}" >"${cache_path}/time"
413
else
414
touch "${cache_path}/time"
415
fi
416
[[ -f ${cache_path}/type ]] || echo -n "${type}" >"${cache_path}/type"
417
[[ -f ${cache_path}/url ]] || echo -n "${url}" >"${cache_path}/url"
418
echo "${cache_path}/data"
419
}
420
421
# Download the file to the cache directory without redirect and print the path.
422
function download_to_cache_without_redirect() {
423
download_to_cache "$1" "NO"
424
}
425
426