Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/hack/update-template-rocky.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 rocky_print_help() {
16
cat <<HELP
17
$(basename "${BASH_SOURCE[0]}"): Update the Rocky Linux image location in the specified templates
18
19
Usage:
20
$(basename "${BASH_SOURCE[0]}") [--version-major <major version>] <template.yaml>...
21
22
Description:
23
This script updates the Rocky Linux image location in the specified templates.
24
If the image location in the template contains a minor version, release date, and job id in the URL,
25
the script replaces it with the latest available minor version, release date, and job id.
26
27
Image location basename format:
28
29
Rocky-<major version>-GenericCloud[.latest|-<type>.latest|-<major version>.<minor version>-<date>.<job id?>].<arch>.qcow2
30
31
Published Rocky Linux image information is fetched from the following URLs:
32
33
https://dl.rockylinux.org/pub/rocky/<major version>/images/<arch>/
34
35
To parsing html, this script requires 'htmlq' or 'pup' command.
36
The downloaded files will be cached in the Lima cache directory.
37
38
Examples:
39
Update the Rocky Linux image location in templates/**.yaml:
40
$ $(basename "${BASH_SOURCE[0]}") templates/**.yaml
41
42
Update the Rocky Linux image location in ~/.lima/rocky/lima.yaml:
43
$ $(basename "${BASH_SOURCE[0]}") ~/.lima/rocky/lima.yaml
44
$ limactl factory-reset rocky
45
46
Update the Rocky Linux image location to major version 9 in ~/.lima/rocky/lima.yaml:
47
$ $(basename "${BASH_SOURCE[0]}") --version-major 9 ~/.lima/rocky/lima.yaml
48
$ limactl factory-reset rocky
49
50
Flags:
51
--version-major <version> Use the specified version. The version must be 8 or later.
52
-h, --help Print this help message
53
HELP
54
}
55
56
# print the URL spec for the given location
57
function rocky_url_spec_from_location() {
58
local location=$1 jq_filter url_spec
59
jq_filter='capture("
60
^https://dl\\.rockylinux\\.org/pub/rocky/(?<path_version>\\d+(\\.\\d+)?)/images/(?<path_arch>[^/]+)/
61
Rocky-(?<major_version>\\d+)-(?<target_vendor>.*)
62
(
63
-(?<type>.*)-(?<major_minor_version>\\d+\\.\\d+)-(?<date_and_ci_job_id>\\d{8}\\.\\d+)|
64
-(?<type_latest>.*)\\.latest|
65
\\.latest
66
)\\.(?<arch>[^.]+).(?<file_extension>.*)$
67
";"x")
68
'
69
url_spec=$(jq -e -r "${jq_filter}" <<<"\"${location}\"")
70
71
jq -e '.path_arch == .arch' <<<"${url_spec}" >/dev/null ||
72
error_exit "Validation failed: .path_arch != .arch: ${location}"
73
echo "${url_spec}"
74
}
75
76
readonly rocky_jq_filter_directory='"https://dl.rockylinux.org/pub/rocky/\(.path_version)/images/\(.path_arch)/"'
77
readonly rocky_jq_filter_filename='
78
"Rocky-\(.major_version)-\(.target_vendor)\(
79
if .date_and_ci_job_id then
80
"-\(.type)-\(.major_minor_version)-\(.date_and_ci_job_id)"
81
else
82
if .type then
83
"-\(.type_latest).latest"
84
else
85
".latest"
86
end
87
end
88
).\(.arch).\(.file_extension)"
89
'
90
91
# print the location for the given URL spec
92
function rocky_location_from_url_spec() {
93
local -r url_spec=$1
94
jq -e -r "${rocky_jq_filter_directory} + ${rocky_jq_filter_filename}" <<<"${url_spec}" ||
95
error_exit "Failed to get the location for ${url_spec}"
96
}
97
98
function rocky_image_directory_from_url_spec() {
99
local -r url_spec=$1
100
jq -e -r "${rocky_jq_filter_directory}" <<<"${url_spec}" ||
101
error_exit "Failed to get the image directory for ${url_spec}"
102
}
103
104
function rocky_image_filename_from_url_spec() {
105
local -r url_spec=$1
106
jq -e -r "${rocky_jq_filter_filename}" <<<"${url_spec}" ||
107
error_exit "Failed to get the image filename for ${url_spec}"
108
}
109
110
#
111
function rocky_latest_image_entry_for_url_spec() {
112
local url_spec=$1 arch major_version_url_spec major_version_image_directory downloaded_page links_in_page latest_minor_version_info
113
arch=$(jq -r '.arch' <<<"${url_spec}")
114
# to detect minor version updates, we need to get the major version URL
115
major_version_url_spec=$(jq -e -r '.path_version = .major_version' <<<"${url_spec}")
116
major_version_image_directory=$(rocky_image_directory_from_url_spec "${major_version_url_spec}")
117
downloaded_page=$(download_to_cache "${major_version_image_directory}")
118
if command -v htmlq >/dev/null; then
119
links_in_page=$(htmlq 'pre a' --attribute href <"${downloaded_page}")
120
elif command -v pup >/dev/null; then
121
links_in_page=$(pup 'pre a attr{href}' <"${downloaded_page}")
122
else
123
error_exit "Please install 'htmlq' or 'pup' to list images from ${major_version_image_directory}"
124
fi
125
latest_minor_version_info=$(jq -e -Rrs --argjson spec "${url_spec}" '
126
[
127
split("\n").[] |
128
capture(
129
"^Rocky-\($spec.major_version)-\($spec.target_vendor)-\($spec.type)" +
130
"-(?<major_minor_version>\($spec.major_version)\\.\\d+)" +
131
"-(?<date_and_ci_job_id>\\d{8}\\.\\d+)\\.\($spec.arch)\\.\($spec.file_extension)$"
132
;"x"
133
) |
134
.version_number_array = ([.major_minor_version | scan("\\d+") | tonumber])
135
] | sort_by(.version_number_array, .date_and_ci_job_id) | last
136
' <<<"${links_in_page}")
137
[[ -n ${latest_minor_version_info} ]] || return
138
local newer_url_spec location sha256sum_location downloaded_sha256sum filename digest
139
# prefer the major_minor_version in the path
140
newer_url_spec=$(jq -e -r ". + ${latest_minor_version_info} | .path_version = .major_minor_version" <<<"${url_spec}")
141
location=$(rocky_location_from_url_spec "${newer_url_spec}")
142
sha256sum_location="${location}.CHECKSUM"
143
downloaded_sha256sum=$(download_to_cache "${sha256sum_location}")
144
filename=$(rocky_image_filename_from_url_spec "${newer_url_spec}")
145
digest="sha256:$(awk "/SHA256 \(${filename}\) =/{print \$4}" "${downloaded_sha256sum}")"
146
[[ -n ${digest} ]] || error_exit "Failed to get the SHA256 digest for ${filename}"
147
json_vars location arch digest
148
}
149
150
function rocky_cache_key_for_image_kernel() {
151
local location=$1 url_spec
152
url_spec=$(rocky_url_spec_from_location "${location}")
153
jq -r '["rocky", .major_minor_version // .major_version, .target_vendor,
154
if .date_and_ci_job_id then "timestamped" else "latest" end,
155
.arch, .file_extension] | join(":")' <<<"${url_spec}"
156
}
157
158
function rocky_image_entry_for_image_kernel() {
159
local location=$1 kernel_is_not_supported=$2 overriding=${3:-"{}"} url_spec image_entry=''
160
[[ ${kernel_is_not_supported} == "null" ]] || echo "Updating kernel information is not supported on Rocky Linux" >&2
161
url_spec=$(rocky_url_spec_from_location "${location}" | jq -r ". + ${overriding}")
162
if jq -e '.date_and_ci_job_id' <<<"${url_spec}" >/dev/null; then
163
image_entry=$(rocky_latest_image_entry_for_url_spec "${url_spec}")
164
else
165
image_entry=$(
166
# shellcheck disable=SC2030
167
location=$(rocky_location_from_url_spec "${url_spec}")
168
location=$(validate_url_without_redirect "${location}")
169
arch=$(jq -r '.path_arch' <<<"${url_spec}")
170
json_vars location arch
171
)
172
fi
173
# shellcheck disable=SC2031
174
if [[ -z ${image_entry} ]]; then
175
error_exit "Failed to get the ${url_spec} image location for ${location}"
176
elif jq -e ".location == \"${location}\"" <<<"${image_entry}" >/dev/null; then
177
echo "Image location is up-to-date: ${location}" >&2
178
else
179
echo "${image_entry}"
180
fi
181
}
182
183
# check if the script is executed or sourced
184
# shellcheck disable=SC1091
185
if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
186
scriptdir=$(dirname "${BASH_SOURCE[0]}")
187
# shellcheck source=./cache-common-inc.sh
188
. "${scriptdir}/cache-common-inc.sh"
189
190
if ! command -v htmlq >/dev/null && ! command -v pup >/dev/null; then
191
error_exit "Please install 'htmlq' or 'pup' to list images from https://dl.rockylinux.org/pub/rocky/<version>/images/<arch>/"
192
fi
193
# shellcheck source=/dev/null # avoid shellcheck hangs on source looping
194
. "${scriptdir}/update-template.sh"
195
else
196
# this script is sourced
197
if ! command -v htmlq >/dev/null && ! command -v pup >/dev/null; then
198
echo "Please install 'htmlq' or 'pup' to list images from https://dl.rockylinux.org/pub/rocky/<version>/images/<arch>/" >&2
199
elif [[ -v SUPPORTED_DISTRIBUTIONS ]]; then
200
SUPPORTED_DISTRIBUTIONS+=("rocky")
201
else
202
declare -a SUPPORTED_DISTRIBUTIONS=("rocky")
203
fi
204
return 0
205
fi
206
207
declare -a templates=()
208
declare overriding="{}"
209
while [[ $# -gt 0 ]]; do
210
case "$1" in
211
-h | --help)
212
rocky_print_help
213
exit 0
214
;;
215
-d | --debug) set -x ;;
216
--version-major)
217
if [[ -n $2 && $2 != -* ]]; then
218
overriding=$(
219
major_version="${2%%.*}"
220
[[ ${major_version} -ge 8 ]] || error_exit "Rocky Linux major version must be 8 or later"
221
# shellcheck disable=2034
222
path_version="${major_version}"
223
json_vars path_version major_version <<<"${overriding}"
224
)
225
shift
226
else
227
error_exit "--version-major requires a value"
228
fi
229
;;
230
--version-major=*)
231
overriding=$(
232
major_version="${1#*=}"
233
major_version="${major_version%%.*}"
234
[[ ${major_version} -ge 8 ]] || error_exit "Rocky Linux major version must be 8 or later"
235
# shellcheck disable=2034
236
path_version="${major_version}"
237
json_vars path_version major_version <<<"${overriding}"
238
)
239
;;
240
*.yaml) templates+=("$1") ;;
241
*)
242
error_exit "Unknown argument: $1"
243
;;
244
esac
245
shift
246
[[ -z ${overriding} ]] && overriding="{}"
247
done
248
249
if [[ ${#templates[@]} -eq 0 ]]; then
250
rocky_print_help
251
exit 0
252
fi
253
254
declare -A image_entry_cache=()
255
256
for template in "${templates[@]}"; do
257
echo "Processing ${template}"
258
# 1. extract location by parsing template using arch
259
yq_filter="
260
.images[] | [.location, .kernel.location, .kernel.cmdline] | @tsv
261
"
262
parsed=$(yq eval "${yq_filter}" "${template}")
263
264
# 3. get the image location
265
arr=()
266
while IFS= read -r line; do arr+=("${line}"); done <<<"${parsed}"
267
locations=("${arr[@]}")
268
for ((index = 0; index < ${#locations[@]}; index++)); do
269
[[ ${locations[index]} != "null" ]] || continue
270
set -e
271
IFS=$'\t' read -r location kernel_location kernel_cmdline <<<"${locations[index]}"
272
set +e # Disable 'set -e' to avoid exiting on error for the next assignment.
273
cache_key=$(
274
set -e # Enable 'set -e' for the next command.
275
rocky_cache_key_for_image_kernel "${location}" "${kernel_location}"
276
) # Check exit status separately to prevent disabling 'set -e' by using the function call in the condition.
277
# shellcheck disable=2181
278
[[ $? -eq 0 ]] || continue
279
image_entry=$(
280
set -e # Enable 'set -e' for the next command.
281
if [[ -v image_entry_cache[${cache_key}] ]]; then
282
echo "${image_entry_cache[${cache_key}]}"
283
else
284
rocky_image_entry_for_image_kernel "${location}" "${kernel_location}" "${overriding}"
285
fi
286
) # Check exit status separately to prevent disabling 'set -e' by using the function call in the condition.
287
# shellcheck disable=2181
288
[[ $? -eq 0 ]] || continue
289
set -e
290
image_entry_cache[${cache_key}]="${image_entry}"
291
if [[ -n ${image_entry} ]]; then
292
[[ ${kernel_cmdline} != "null" ]] &&
293
jq -e 'has("kernel")' <<<"${image_entry}" >/dev/null &&
294
image_entry=$(jq ".kernel.cmdline = \"${kernel_cmdline}\"" <<<"${image_entry}")
295
echo "${image_entry}" | jq
296
limactl edit --log-level error --set "
297
.images[${index}] = ${image_entry}|
298
(.images[${index}] | ..) style = \"double\"
299
" "${template}"
300
fi
301
done
302
done
303
304