Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/hack/update-template-archlinux.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 archlinux_print_help() {
16
cat <<HELP
17
$(basename "${BASH_SOURCE[0]}"): Update the Arch-Linux image location in the specified templates
18
19
Usage:
20
$(basename "${BASH_SOURCE[0]}") <template.yaml>...
21
22
Description:
23
This script updates the Arch-Linux 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
26
Image location basename format: Arch-Linux-<arch>-cloudimg[-<date>.<CI_JOB_ID>].qcow2
27
28
Published Arch-Linux image information is fetched from the following URLs:
29
30
x86_64:
31
listing: https://gitlab.archlinux.org/api/v4/projects/archlinux%2Farch-boxes/packages
32
details: https://gitlab.archlinux.org/api/v4/projects/archlinux%2Farch-boxes/packages/:package_id/package_files
33
34
aarch64:
35
https://github.com/mcginty/arch-boxes-arm/releases/
36
37
Using 'gh' CLI tool for fetching the latest release from GitHub.
38
39
Examples:
40
Update the Arch-Linux image location in templates/**.yaml:
41
$ $(basename "${BASH_SOURCE[0]}") templates/**.yaml
42
43
Update the Arch-Linux image location in ~/.lima/archlinux/lima.yaml:
44
$ $(basename "${BASH_SOURCE[0]}") ~/.lima/archlinux/lima.yaml
45
$ limactl factory-reset archlinux
46
47
Flags:
48
-h, --help Print this help message
49
HELP
50
}
51
52
# print the URL spec for the given location
53
# shellcheck disable=SC2034
54
function archlinux_url_spec_from_location() {
55
local location=$1 location_basename arch flavor source date_and_ci_job_id='' file_extension
56
location_basename=$(basename "${location}")
57
arch=$(echo "${location_basename}" | cut -d- -f3)
58
flavor=$(echo "${location_basename}" | cut -d- -f4 | cut -d. -f1)
59
case "${location}" in
60
https://geo.mirror.pkgbuild.com/images/*)
61
source="geo.mirror.pkgbuild.com"
62
local -r date_and_ci_job_id_pattern='[0-9]{8}\.[0-9]+'
63
if [[ ${location} =~ ${date_and_ci_job_id_pattern} ]]; then
64
date_and_ci_job_id="${BASH_REMATCH[0]}"
65
fi
66
if [[ ${location_basename} =~ ${date_and_ci_job_id_pattern} ]]; then
67
file_extension=${location_basename##*"${BASH_REMATCH[0]}".}
68
else
69
file_extension=${location_basename#*.}
70
fi
71
;;
72
https://github.com/mcginty/arch-boxes-arm/releases/download/*)
73
source="github.com/mcginty/arch-boxes-arm"
74
local -r date_pattern='[0-9]{8}'
75
if [[ ${location} =~ ${date_pattern} ]]; then
76
date_and_ci_job_id="${BASH_REMATCH[0]}"
77
file_extension=${location_basename#*"${date_and_ci_job_id}".*.}
78
else
79
error_exit "Failed to extract date from ${location}"
80
fi
81
;;
82
*)
83
# Unsupported location
84
return 1
85
;;
86
esac
87
json_vars source arch flavor date_and_ci_job_id file_extension
88
}
89
90
# print the location for the given URL spec
91
function archlinux_location_from_url_spec() {
92
local url_spec=$1 source arch flavor date_and_ci_job_id file_extension location=''
93
source=$(jq -r '.source' <<<"${url_spec}")
94
arch=$(jq -r '.arch' <<<"${url_spec}")
95
flavor=$(jq -r '.flavor' <<<"${url_spec}")
96
date_and_ci_job_id=$(jq -r '.date_and_ci_job_id' <<<"${url_spec}")
97
98
file_extension=$(jq -r '.file_extension' <<<"${url_spec}")
99
case "${source}" in
100
geo.mirror.pkgbuild.com)
101
location="https://geo.mirror.pkgbuild.com/images/"
102
if [[ -n ${date_and_ci_job_id} ]]; then
103
location+="v${date_and_ci_job_id}/Arch-Linux-${arch}-${flavor}-${date_and_ci_job_id}.${file_extension}"
104
else
105
location+="latest/Arch-Linux-${arch}-${flavor}.${file_extension}"
106
fi
107
;;
108
github.com/mcginty/arch-boxes-arm) ;;
109
*)
110
error_exit "Unsupported source: ${source}"
111
;;
112
esac
113
echo "${location}"
114
}
115
116
# returns the image entry for the latest image in the gitlab mirror
117
function archlinux_image_entry_for_image_kernel_gitlab_mirror() {
118
local location=$1 url_spec=$2 arch flavor date_and_ci_job_id gitlab_package_api_base latest_package_id jq_filter latest_package_file file_name digest updated_url_spec
119
arch=$(jq -r '.arch' <<<"${url_spec}")
120
if ! jq -e '.date_and_ci_job_id' <<<"${url_spec}" >/dev/null; then
121
json_vars location arch
122
return 1
123
fi
124
flavor=$(jq -r '.flavor' <<<"${url_spec}")
125
date_and_ci_job_id=$(jq -r '.date_and_ci_job_id' <<<"${url_spec}")
126
file_extension=$(jq -r '.file_extension' <<<"${url_spec}")
127
gitlab_package_api_base="https://gitlab.archlinux.org/api/v4/projects/archlinux%2Farch-boxes/packages"
128
latest_package_id=$(curl --silent --show-error "${gitlab_package_api_base}" | jq -r 'last|.id') || error_exit "Failed to fetch latest package_id"
129
jq_filter="
130
.[]|select(.file_name|test(\"^Arch-Linux-${arch}-${flavor}-.*\\\.${file_extension}\$\"))
131
"
132
latest_package_file=$(curl -s "${gitlab_package_api_base}/${latest_package_id}/package_files" | jq -r "${jq_filter}") ||
133
error_exit "Failed to fetch latest package_file"
134
file_name=$(jq -r '.file_name' <<<"${latest_package_file}")
135
digest="sha256:$(jq -r '.file_sha256' <<<"${latest_package_file}")"
136
local -r date_and_ci_job_id_pattern='[0-9]{8}\.[0-9]+'
137
[[ ${file_name} =~ ${date_and_ci_job_id_pattern} ]] || error_exit "Failed to extract date_and_ci_job_id from ${file_name}"
138
date_and_ci_job_id="${BASH_REMATCH[0]}"
139
updated_url_spec=$(json_vars date_and_ci_job_id <<<"${url_spec}")
140
location=$(archlinux_location_from_url_spec "${updated_url_spec}")
141
location=$(validate_url_without_redirect "${location}")
142
json_vars location arch digest
143
}
144
145
# returns the image entry for the latest image in the GitHub repo
146
function archlinux_image_entry_for_image_kernel_github_com() {
147
local location=$1 url_spec=$2 arch flavor file_extension repo jq_filter latest_location downloaded_img digest
148
arch=$(jq -r '.arch' <<<"${url_spec}")
149
if ! jq -e '.date_and_ci_job_id' <<<"${url_spec}" >/dev/null; then
150
json_vars location arch
151
return 1
152
fi
153
flavor=$(jq -r '.flavor' <<<"${url_spec}")
154
file_extension=$(jq -r '.file_extension' <<<"${url_spec}")
155
command -v gh >/dev/null || error_exit "gh is required for fetching the latest release from GitHub, but it's not installed"
156
local -r repo_pattern='github.com/(.*)/releases/download/(.*)'
157
if [[ ${location} =~ ${repo_pattern} ]]; then
158
repo="${BASH_REMATCH[1]}"
159
else
160
error_exit "Failed to extract repo and release from ${location}"
161
fi
162
jq_filter=".assets[]|select(.name|test(\"^Arch-Linux-${arch}-${flavor}-.*\\\.${file_extension}\$\"))|.url"
163
latest_location=$(gh release view --repo "${repo}" --json assets --jq "${jq_filter}")
164
[[ -n ${latest_location} ]] || error_exit "Failed to fetch the latest release URL from ${repo}"
165
if [[ ${location} == "${latest_location}" ]]; then
166
json_vars location arch
167
return
168
fi
169
location=${latest_location}
170
downloaded_img=$(download_to_cache_without_redirect "${latest_location}")
171
# shellcheck disable=SC2034
172
digest="sha512:$(sha512sum "${downloaded_img}" | cut -d' ' -f1)"
173
json_vars location arch digest
174
}
175
176
function archlinux_cache_key_for_image_kernel() {
177
local location=$1 url_spec
178
url_spec=$(archlinux_url_spec_from_location "${location}")
179
jq -r '["archlinux", .source, .arch, .date_and_ci_job_id // empty]|join(":")' <<<"${url_spec}"
180
}
181
182
function archlinux_image_entry_for_image_kernel() {
183
local location=$1 url_spec source image_entry=''
184
url_spec=$(archlinux_url_spec_from_location "${location}")
185
source=$(jq -r '.source' <<<"${url_spec}")
186
case "${source}" in
187
geo.mirror.pkgbuild.com)
188
image_entry=$(archlinux_image_entry_for_image_kernel_gitlab_mirror "${location}" "${url_spec}")
189
;;
190
github.com/mcginty/arch-boxes-arm)
191
image_entry=$(archlinux_image_entry_for_image_kernel_github_com "${location}" "${url_spec}")
192
;;
193
*) error_exit "Unsupported source: ${source}" ;;
194
esac
195
if [[ -z ${image_entry} ]]; then
196
error_exit "Failed to get the ${url_spec} image location for ${location}"
197
elif jq -e ".location == \"${location}\"" <<<"${image_entry}" >/dev/null; then
198
echo "Image location is up-to-date: ${location}" >&2
199
else
200
echo "${image_entry}"
201
fi
202
}
203
204
# check if the script is executed or sourced
205
# shellcheck disable=SC1091
206
if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
207
scriptdir=$(dirname "${BASH_SOURCE[0]}")
208
# shellcheck source=./cache-common-inc.sh
209
. "${scriptdir}/cache-common-inc.sh"
210
211
# shellcheck source=/dev/null # avoid shellcheck hangs on source looping
212
. "${scriptdir}/update-template.sh"
213
else
214
# this script is sourced
215
if [[ -v SUPPORTED_DISTRIBUTIONS ]]; then
216
SUPPORTED_DISTRIBUTIONS+=("archlinux")
217
else
218
declare -a SUPPORTED_DISTRIBUTIONS=("archlinux")
219
fi
220
return 0
221
fi
222
223
declare -a templates=()
224
declare overriding="{}"
225
while [[ $# -gt 0 ]]; do
226
case "$1" in
227
-h | --help)
228
archlinux_print_help
229
exit 0
230
;;
231
-d | --debug) set -x ;;
232
*.yaml) templates+=("$1") ;;
233
*)
234
error_exit "Unknown argument: $1"
235
;;
236
esac
237
shift
238
[[ -z ${overriding} ]] && overriding="{}"
239
done
240
241
if [[ ${#templates[@]} -eq 0 ]]; then
242
archlinux_print_help
243
exit 0
244
fi
245
246
declare -A image_entry_cache=()
247
248
for template in "${templates[@]}"; do
249
echo "Processing ${template}"
250
# 1. extract location by parsing template using arch
251
yq_filter="
252
.images[] | [.location, .kernel.location, .kernel.cmdline] | @tsv
253
"
254
parsed=$(yq eval "${yq_filter}" "${template}")
255
256
# 3. get the image location
257
arr=()
258
while IFS= read -r line; do arr+=("${line}"); done <<<"${parsed}"
259
locations=("${arr[@]}")
260
for ((index = 0; index < ${#locations[@]}; index++)); do
261
[[ ${locations[index]} != "null" ]] || continue
262
set -e
263
IFS=$'\t' read -r location kernel_location kernel_cmdline <<<"${locations[index]}"
264
set +e # Disable 'set -e' to avoid exiting on error for the next assignment.
265
cache_key=$(
266
set -e # Enable 'set -e' for the next command.
267
archlinux_cache_key_for_image_kernel "${location}" "${kernel_location}"
268
) # Check exit status separately to prevent disabling 'set -e' by using the function call in the condition.
269
# shellcheck disable=2181
270
[[ $? -eq 0 ]] || continue
271
image_entry=$(
272
set -e # Enable 'set -e' for the next command.
273
if [[ -v image_entry_cache[${cache_key}] ]]; then
274
echo "${image_entry_cache[${cache_key}]}"
275
else
276
archlinux_image_entry_for_image_kernel "${location}" "${kernel_location}" "${overriding}"
277
fi
278
) # Check exit status separately to prevent disabling 'set -e' by using the function call in the condition.
279
# shellcheck disable=2181
280
[[ $? -eq 0 ]] || continue
281
set -e
282
image_entry_cache[${cache_key}]="${image_entry}"
283
if [[ -n ${image_entry} ]]; then
284
[[ ${kernel_cmdline} != "null" ]] &&
285
jq -e 'has("kernel")' <<<"${image_entry}" >/dev/null &&
286
image_entry=$(jq ".kernel.cmdline = \"${kernel_cmdline}\"" <<<"${image_entry}")
287
echo "${image_entry}" | jq
288
limactl edit --log-level error --set "
289
.images[${index}] = ${image_entry}|
290
(.images[${index}] | ..) style = \"double\"
291
" "${template}"
292
fi
293
done
294
done
295
296