Skip to content

Commit 54125da

Browse files
authored
Merge pull request #2806 from norio-nomura/update-template-rocky.sh
Add `hack/update-template-rocky.sh`
2 parents ab4b4b5 + 81f037f commit 54125da

File tree

2 files changed

+301
-0
lines changed

2 files changed

+301
-0
lines changed

hack/update-template-rocky.sh

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

hack/update-template.sh

+2
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ if [[ ${BASH_SOURCE[0]} == "${0}" ]]; then
154154
. "${scriptdir}/update-template-centos-stream.sh"
155155
# shellcheck source=./update-template-almalinux.sh
156156
. "${scriptdir}/update-template-almalinux.sh"
157+
# shellcheck source=./update-template-rocky.sh
158+
. "${scriptdir}/update-template-rocky.sh"
157159
else
158160
# this script is sourced
159161
return 0

0 commit comments

Comments
 (0)