-
-
Save ryran/072409b1b7efd5018683a8c45e019652 to your computer and use it in GitHub Desktop.
#!/bin/bash | |
# ocp4-chk-upgrade-channel v1.4 last mod 2022/11/16 | |
# https://gist.github.com/ryran/072409b1b7efd5018683a8c45e019652 | |
# Copyright 2020, 2021, 2022 Ryan Sawhill Aroha <[email protected]> | |
# | |
# This program is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation, either version 3 of the License, or | |
# (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
# General Public License <gnu.org/licenses/gpl.html> for more details. | |
export LC_ALL=en_US.UTF-8 | |
# Get version from line #2 | |
version=$(sed '2q;d' $0) | |
# Colors | |
[[ $OCP4_CHK_UPGRADE_NO_COLORS == 1 || $BASH_VERSINFO -lt 4 ]] || | |
declare -A c=( | |
[z]='\033[0;0m' # zero (reset) | |
[Q]='\033[0;0m\033[1;1m' # bold | |
[dim]='\033[0;0m\033[2m' # dim | |
[rdim]='\033[2;31m' [r]='\033[0;31m' [R]='\033[1;31m' # red | |
[gdim]='\033[2;32m' [g]='\033[0;32m' [G]='\033[1;32m' # green | |
[ydim]='\033[2;33m' [y]='\033[0;33m' [Y]='\033[1;33m' # yellow | |
[bdim]='\033[2;34m' [b]='\033[0;34m' [B]='\033[1;34m' # blue | |
[mdim]='\033[2;35m' [m]='\033[0;35m' [M]='\033[1;35m' # maroon | |
[cdim]='\033[2;36m' [c]='\033[0;36m' [C]='\033[1;36m' # cyan | |
) | |
# Return codes | |
declare -A RCs=( | |
[majorvers_chan_missing]=9 | |
[release_in_no_channels]=8 | |
[release_has_no_upgrades]=7 | |
[release_has_no_releasetxt]=6 | |
[release_highest_channel_is_candidate]=5 | |
[release_highest_channel_is_fast]=4 | |
) | |
# Fancy versions of upgrade channel-types | |
declare -A fancyChan=( | |
[eus]="${c[G]}🌲 ✔ eus 🌲 ✔ " | |
[stable]="${c[G]}✔✔✔ stable ✔✔✔" | |
[fast]="${c[Y]}⚠️ ⚠️ fast ⚠️ ⚠️ " | |
[candidate]="${c[R]}⛔ ☢️ candidate ⛔ ☢️ " | |
[prerelease]="${c[R]}⛔ ☢️ prerelease ⛔ ☢️ " | |
[null]="${c[R]}🛑 🚫 U N K N O W N 🛑 🚫 " | |
) | |
# Main stderr printing function | |
print() { | |
# only print if in verbose mode | |
if [[ $1 == --verbose ]]; then | |
[[ $verbose ]] || return | |
shift | |
fi | |
local echo_opts= | |
while [[ $1 =~ ^- ]]; do | |
echo_opts+="$1 " | |
shift | |
done | |
echo -e $echo_opts "$@" >&2 | |
} | |
get_releasetxt_url() { | |
echo https://mirror.openshift.com/pub/openshift-v4/clients/ocp/${1}/release.txt | |
} | |
# Whoami | |
me=${0##*/} | |
# This is the API we talk to | |
api=https://api.openshift.com/api/upgrades_info/v1 | |
# Just for help page | |
releaseTxtDummyUrl=$(get_releasetxt_url X.Y.Z) | |
# Default exit | |
rc=0 | |
# Whether we found a release.txt | |
releasetxtPublished= | |
# Blah | |
dimGreenCheck="${c[gdim]}✔${c[z]}" | |
dimYellowCheck="${c[ydim]}✔${c[z]}" | |
dimRedX="${c[rdim]}✘${c[z]}" | |
# Opts for getopt | |
opts_short=hiec:a:v | |
opts_long=help,ignore-releasetxt,allow-non-stable,chan:,arch:,verbose | |
# Populated from args/opts | |
ignoreMissingReleaseTxt= allowNonStable= channel= arch= verbose= | |
opMode= releaseMajor= releaseMinor= release= major= | |
# ██╗ ██╗███████╗██╗ ██████╗ | |
# ██║ ██║██╔════╝██║ ██╔══██╗ | |
# ███████║█████╗ ██║ ██████╔╝ | |
# ██╔══██║██╔══╝ ██║ ██╔═══╝ | |
# ██║ ██║███████╗███████╗██║ | |
# ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ | |
usage() { | |
print "$me: $@" | |
exit 1 | |
} | |
halp() { | |
local mode1_usage mode2_usage global_usage operating_mode mode1_line mode2_line halp_short halp halp_addendum | |
mode1_usage="$me [OPTS] [--allow-non-stable] ${c[B]}X.Y.Z${c[z]}" | |
mode1_line="Determine highest channel in which the X.Y.Z release can be found" | |
mode2_usage="$me [OPTS] [-c CHANNEL] ${c[C]}X.Y${c[z]}" | |
mode2_line="Determine newest release of X.Y version found in a channel" | |
mode3_usage="$me [OPTS] [-c CHANNEL] ${c[M]}X.Y.Z X.Y${c[z]}" | |
mode3_line="Determine available upgrade paths from X.Y.Z release to a different X.Y version" | |
operating_mode="" | |
halp_short=$(cat <<-EOF | |
${c[Q]}Usage: $mode1_usage | |
$mode1_line | |
${c[Q]}Usage: $mode2_usage | |
$mode2_line | |
${c[Q]}Usage: $mode3_usage | |
$mode3_line | |
EOF | |
) | |
halp=$(cat <<-EOF | |
${c[Q]}Usage: $me ${c[B]}X.Y.Z${c[Q]} | ${c[C]}X.Y${c[Q]} | ${c[M]}X.Y.Z X.Y${c[z]} | |
${c[Q]}Leverage ${api#https://}/openapi to inspect OCP versions${c[z]} | |
Operating mode is chosen based on presence of ${c[b]}X.Y.Z${c[z]} or ${c[c]}X.Y${c[z]} or ${c[m]}both${c[z]}. | |
${c[B]}Mode 1: $mode1_usage | |
$mode1_line | |
${c[b]}X.Y.Z${c[z]} (e.g. "4.6.12") | |
Searches for \$ARCH release of X.Y.Z release in channels: stable, fast, candidate | |
Prints to stdout the *highest* channel in which that release is tagged (e.g. "stable") | |
Exit codes: | |
• ${c[R]}${RCs[release_in_no_channels]}${c[z]} = release in NO channels | |
• ${c[r]}${RCs[release_has_no_releasetxt]}${c[z]} = release has no published release.txt (see "-i") | |
• ${c[m]}${RCs[release_highest_channel_is_candidate]}${c[z]} = highest channel: candidate (or prerelease) | |
• ${c[y]}${RCs[release_highest_channel_is_fast]}${c[z]} = highest channel: fast | |
• ${c[g]}0${c[z]} = in stable channel | |
${c[b]}-e, --allow-non-stable${c[z]} | |
Prevent throwing exit code ${c[m]}${RCs[release_highest_channel_is_candidate]}${c[z]} or ${c[y]}${RCs[release_highest_channel_is_fast]}${c[z]} when release is only in candidate or fast channel | |
Note that a missing release.txt could still trigger exit code ${c[r]}${RCs[release_has_no_releasetxt]}${c[z]} unless "-i" is also used | |
${c[C]}Mode 2: $mode2_usage | |
$mode2_line | |
${c[c]}X.Y${c[z]} (e.g. "4.6") | |
Searches for newest \$ARCH release of X.Y version in \$CHANNEL | |
Prints exact X.Y.Z release to stdout (e.g. "4.6.12") | |
Exit codes: | |
• ${c[R]}${RCs[majorvers_chan_missing]}${c[z]} = \$CHANNEL-X.Y doesn't exist for \$ARCH | |
• ${c[r]}${RCs[release_has_no_releasetxt]}${c[z]} = the latest release has no published release.txt (see "-i") | |
• ${c[g]}0${c[z]} = found latest good release | |
${c[c]}-c, --chan CHANNEL${c[z]} (upgrade channel type) | |
Allow selecting alternative to the default of "stable" | |
Other choices: eus, fast, candidate (or alias "cand"), prerelease | |
${c[M]}Mode 3: $mode3_usage | |
$mode3_line | |
${c[m]}X.Y.Z X.Y${c[z]} | |
Lists all \$ARCH X.Y releases in a channel that allow upgrading from X.Y.Z | |
By default, search starts in stable; if no results, then fast; then candidate | |
Prints to stdout a list of release numbers in ascending order | |
Exit codes: | |
• ${c[R]}${RCs[majorvers_chan_missing]}${c[z]} = none of the *-X.Y channels exist for \$ARCH | |
with -c/--chan: \$CHANNEL-X.Y doesn't exist for \$ARCH | |
• ${c[r]}${RCs[release_has_no_upgrades]}${c[z]} = none of the *-X.Y channels have valid upgrade paths for \$ARCH | |
with -c/--chan: no valid upgrade paths in \$CHANNEL-X.Y for \$ARCH | |
• ${c[g]}0${c[z]} = found valid upgrade paths in one of the *-X.Y channels for \$ARCH | |
with -c/--chan: found valid upgrade paths in \$CHANNEL-X.Y for \$ARCH | |
${c[m]}-c, --chan CHANNEL${c[z]} (upgrade channel type) | |
Look for upgrade paths only in \$CHANNEL (instead of searching through stable, fast, candidate) | |
Choices: eus, stable, fast, candidate (or alias "cand"), prerelease | |
${c[Q]}Mode-independent options${c[z]} | |
-i, --ignore-releasetxt | |
Allow version-specific release.txt to NOT be present at: | |
• ${releaseTxtDummyUrl#https://} | |
Otherwise, if it's missing, exit code ${c[r]}${RCs[release_has_no_releasetxt]}${c[z]} will be thrown, even if: | |
• ${c[b]}[mode 1]${c[z]} X.Y.Z release is tagged into one of the 3 main channels | |
• ${c[c]}[mode 2]${c[z]} the latest X.Y release was found | |
-a, --arch ARCH (architecture) | |
Allow selecting alternative to default of "amd64" | |
-v, --verbose | |
Print extra detail to stderr, including more about what's happening + full json release details | |
${c[c]}[mode 2]${c[z]} Also print list of all releases in \$CHANNEL | |
${c[m]}[mode 3]${c[z]} Also print full json details of each release | |
EOF | |
) | |
halp_addendum=$(cat <<-EOF | |
${c[Q]}Notes${c[z]} | |
• In all operating modes, the simple answer (if found) is printed to stdout; fancier explanatory | |
output will always be printed to stderr. | |
• On eus channels: one could argue that modes 1 & 3 should check & treat "eus" channels as | |
*higher* than "stable" ... rsaw is ambivalent about this. Feel free to contact him. | |
• On prerelease channels: as of 2021-01, these were only used in 4.1 & 4.2, so are ignored. | |
• Export ${c[r]}OCP4_CHK_UPGRADE_NO_COLORS=1${c[z]} to disable colors. | |
• You're looking at the full help page, as shown when executed with "--help" | |
To see the same without Notes & Requirements, execute with "-h" | |
For the most succinct usage summary, execute with no args | |
${c[Q]}Requirements${c[z]} | |
• curl | |
• jq (https://github.com/stedolan/jq/releases) | |
• getopt (GNU) | |
EOF | |
) | |
local rc=0 | |
if [[ $1 == --long ]]; then | |
print "$halp\n\n$halp_addendum\n" | |
print "${c[dim]}${version:2}${c[z]}" | |
print "${c[dim]}https://gist.github.com/ryran/072409b1b7efd5018683a8c45e019652${c[z]}" | |
elif [[ $1 == --mid ]]; then | |
print "$halp\n" | |
print "${c[dim]}Help: run with ${c[z]}--help${c[dim]} to see extra notes; for simple usage, run w/ no args${c[z]}" | |
print "${c[dim]}Disable colors: export ${c[z]}OCP4_CHK_UPGRADE_NO_COLORS=1" | |
print "\n${c[dim]}${version:2}${c[z]}" | |
print "${c[dim]}https://gist.github.com/ryran/072409b1b7efd5018683a8c45e019652${c[z]}" | |
else | |
if [[ $1 != --short ]]; then | |
print "${c[R]}$@${c[z]}\n" | |
rc=1 | |
fi | |
print "$halp_short\n" | |
print "${c[dim]}See help: run with ${c[z]}-h" | |
print "${c[dim]}Disable colors: export ${c[z]}OCP4_CHK_UPGRADE_NO_COLORS=1" | |
fi | |
exit $rc | |
} | |
validate_url() { | |
local errs | |
if errs=$(curl --fail -o /dev/null -LSsI $1 2>&1); then | |
[[ $verbose ]] && print "$dimGreenCheck" | |
return | |
else | |
print "\n$indent${c[R]}✘ Failed to get $1${c[z]}${c[r]}\n$indent $errs${c[z]}" | |
return 2 | |
fi | |
} | |
validate_releasetxt() { | |
local url=$(get_releasetxt_url $1) failtxt= | |
if [[ $ignoreMissingReleaseTxt ]]; then | |
local color=${c[ydim]} COLOR=${c[y]} | |
else | |
local color=${c[r]} COLOR=${c[R]} | |
fi | |
if [[ $verbose ]]; then | |
print -n "${c[dim]}Checking for ${url} ...${c[z]} " | |
failtxt="${COLOR}✘${c[z]}\n ${COLOR}Failed to get release.txt" | |
else | |
failtxt="${COLOR}✘ Failed to get $url" | |
fi | |
failtxt+="${c[z]}\n ${color}This would be bad for a normal public release${c[z]}\n ${color}" | |
[[ $ignoreMissingReleaseTxt ]] || failtxt+="You can use ${c[Q]}-i/--ignore-releasetxt${c[z]}${color} to prevent this failure from throwing rc ${RCs[release_has_no_releasetxt]}${c[z]}\n ${color}" | |
if failtxt+=$(curl --fail -o /dev/null -LSsI $url 2>&1); then | |
[[ $verbose ]] && print "$dimGreenCheck" | |
return 0 | |
else | |
print "${failtxt}${c[z]}" | |
# If this is set, return success; otherwise failure | |
[[ $ignoreMissingReleaseTxt ]] | |
return | |
fi | |
} | |
# ██████╗ ██████╗ █████╗ ██████╗ ██╗ ██╗ | |
# ██╔════╝ ██╔══██╗██╔══██╗██╔══██╗██║ ██║ | |
# ██║ ███╗██████╔╝███████║██████╔╝███████║ | |
# ██║ ██║██╔══██╗██╔══██║██╔═══╝ ██╔══██║ | |
# ╚██████╔╝██║ ██║██║ ██║██║ ██║ ██║ | |
# ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ | |
graph_channel_upgrades_info() { | |
local channel=$1 major=$2 minor=$3 Arch=${arch% } | |
local chan=$channel-$major | |
local url="$api/graph?channel=$chan&arch=${Arch:-amd64}" | |
local out= numReleases= err= | |
out=$(curl -sSLH "Accept: application/json" "$url") | |
numReleases=$(jq -e '.nodes | length' <<<$out) || numReleases=0 | |
if (( numReleases == 0)); then | |
[[ $verbose ]] && print -n "${c[r]}✘${c[z]}\n ${c[r]}" || print -n "${c[r]}✘ " | |
print "API gave no results for ${arch}$chan${c[z]}${c[rdim]}\n Failed to get $url${c[z]}" | |
return 2 | |
fi | |
if [[ $minor ]]; then | |
if jq -Me --arg vers "$major.$minor" '.nodes[] | select(.version == $vers)' &>/dev/null <<<$out; then | |
print --verbose "$dimGreenCheck" | |
elif jq -Me --arg vers "$major.$minor" '.nodes[] | select(.version |startswith($vers))' >&2 <<<$out; then | |
print --verbose "$dimYellowCheck" | |
else | |
err=1 | |
print --verbose "$dimRedX" | |
fi | |
else | |
print --verbose "$dimGreenCheck" | |
fi | |
# if [[ $DEBUG ]]; then | |
# print -n "${c[dim]}" | |
# jq -M >&2 <<<$out | |
# print -n "${c[z]}" | |
# fi | |
echo "$out" | |
return ${err:-0} | |
} | |
# ███╗ ███╗ ██████╗ ██████╗ ███████╗ ██╗ | |
# ████╗ ████║██╔═══██╗██╔══██╗██╔════╝ ███║ | |
# ██╔████╔██║██║ ██║██║ ██║█████╗ ╚██║ | |
# ██║╚██╔╝██║██║ ██║██║ ██║██╔══╝ ██║ | |
# ██║ ╚═╝ ██║╚██████╔╝██████╔╝███████╗ ██║ | |
# ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ | |
main_mode1() { | |
local graph= | |
print "${c[Q]}OCP ${arch}v$releaseMajor release $releaseMinor${c[z]}" | |
# Make sure release exists | |
validate_releasetxt $release && releasetxtPublished=1 | |
# Check upgrade channel | |
for channel in stable fast candidate; do | |
print --verbose -n "${c[dim]}Querying upgrades_info API: scanning ${arch}releases in $channel-$releaseMajor for $release ...${c[z]} " | |
graph=$(graph_channel_upgrades_info $channel $releaseMajor $releaseMinor) && break | |
done || channel=null | |
channelMsg="${fancyChan[$channel]}" | |
warnNonStable="\n${c[y]}⚠️ WARNING: Release has not been tagged into the stable channel" | |
case $channel in | |
fast) | |
[[ $allowNonStable ]] || rc=${RCs[release_highest_channel_is_fast]} | |
channelMsg+=$warnNonStable ;; | |
candidate|prerelease) | |
[[ $allowNonStable ]] || rc=${RCs[release_highest_channel_is_candidate]} | |
channelMsg+=$warnNonStable ;; | |
null) | |
print "\n${c[R]}🛑 ERROR: Failed to find release in any of the standard upgrade channels!${c[z]}" | |
exit ${RCs[release_in_no_channels]} ;; | |
esac | |
[[ $verbose ]] && jq -Ce --arg vers $release '.nodes[] | select(.version == $vers)' <<<$graph >&2 | |
print "${c[Q]}Highest channel: $channelMsg${c[z]}" | |
echo $channel | |
} | |
# ███╗ ███╗ ██████╗ ██████╗ ███████╗ ██████╗ | |
# ████╗ ████║██╔═══██╗██╔══██╗██╔════╝ ╚════██╗ | |
# ██╔████╔██║██║ ██║██║ ██║█████╗ █████╔╝ | |
# ██║╚██╔╝██║██║ ██║██║ ██║██╔══╝ ██╔═══╝ | |
# ██║ ╚═╝ ██║╚██████╔╝██████╔╝███████╗ ███████╗ | |
# ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚══════╝ | |
main_mode2() { | |
local graph= | |
print "${c[Q]}OCP ${arch}v$major releases in channel ${fancyChan[$channel]}${c[z]}" | |
print --verbose -n "${c[dim]}Querying upgrades_info API: getting ${arch}releases from $channel-$major ...${c[z]} " | |
graph=$(graph_channel_upgrades_info $channel $major) || exit ${RCs[majorvers_chan_missing]} | |
if [[ $verbose ]]; then | |
jq -C --arg vers ${major} '[ .nodes[].version | select( . | startswith($vers) ) ] | sort_by( . | [splits("[-.]")] | map(tonumber? // .) )' >&2 <<<$graph | |
fi | |
newest=$(jq -rMe --arg vers ${major} '[ .nodes[].version | select( . | startswith($vers) ) ] | sort_by( . | [splits("[-.]")] | map(tonumber? // .) )[-1]' <<<$graph) | |
print "${c[Q]}Newest release: $newest${c[z]}" | |
# Make sure release exists | |
validate_releasetxt $newest && releasetxtPublished=1 | |
if [[ $verbose ]]; then | |
jq -Ce --arg vers $newest '.nodes[] | select(.version == $vers)' >&2 <<<$graph | |
fi | |
echo "$newest" | |
} | |
# ███╗ ███╗ ██████╗ ██████╗ ███████╗ ██████╗ | |
# ████╗ ████║██╔═══██╗██╔══██╗██╔════╝ ╚════██╗ | |
# ██╔████╔██║██║ ██║██║ ██║█████╗ █████╔╝ | |
# ██║╚██╔╝██║██║ ██║██║ ██║██╔══╝ ╚═══██╗ | |
# ██║ ╚═╝ ██║╚██████╔╝██████╔╝███████╗ ██████╔╝ | |
# ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═════╝ | |
main_mode3() { | |
local channels= chan= done=0 graph= out= sortedOnlyMajor= newest= num= s allowedTxt gotgood= | |
if [[ $channel ]]; then | |
channels=$channel | |
else | |
# Only check eus in 4.6+ | |
[[ ${major#*.} -ge 6 ]] && channels="eus stable fast candidate" || channels="stable fast candidate" | |
fi | |
for chan in $channels; do | |
# Print a separator if we've already done some channels | |
(( done > 0 )) && print "${c[dim]}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c[z]}\n" | |
# Increment number of channels we've done | |
(( done++ )) | |
print --verbose -n "${c[dim]}Querying upgrades_info API: getting ${arch}releases from $chan-$major ...${c[z]} " | |
if ! graph=$(graph_channel_upgrades_info $chan $major); then | |
rc=${RCs[majorvers_chan_missing]} | |
[[ $channel ]] && { print "\n${c[c]}Try omitting -c/--chan or selecting a different major release${c[z]}"; exit $rc; } | |
[[ $chan == $channels ]] && { print "\n${c[y]}Try selecting a different major release${c[z]}"; exit $rc; } | |
continue | |
fi | |
# Got the initial logic from https://openshift.tips/upgrades/ | |
out=$(jq -M --arg vers $release '. as $graph | $graph.nodes | map(.version == $vers) | index(true) as $orig | $graph.edges | map(select(.[0] == $orig)[1]) | map($graph.nodes[.])' <<<$graph) | |
# Now let's sort by version number ascending and prune the ones that aren't the right major version | |
if sortedOnlyMajor=$(jq -Me --arg major $major 'sort_by( .version | [splits("[-.]")] | map(tonumber? // .) )[] | select( .version | startswith($major) )' <<<$out); then | |
gotgood=1 | |
num=$(jq -s 'length' <<<$sortedOnlyMajor) | |
(( num == 1 )) && s= || s=s | |
print "${c[gdim]}✔ API lists $num allowed upgrade$s for v$release in ${arch}$chan-$major${c[z]}" | |
print "${c[Q]}OCP ${arch}v${releaseMajor} release $releaseMinor valid upgrade paths in $major channel ${fancyChan[$chan]}${c[z]}" | |
if [[ $verbose ]]; then | |
jq -Cs <<<$sortedOnlyMajor >&2 | |
fi | |
jq -Mr '.version' <<<$sortedOnlyMajor | |
else | |
print "${c[ydim]}✘ API lists 0 allowed upgrades for v$release in ${arch}$chan-$major${c[z]}" | |
rc=${RCs[release_has_no_upgrades]} | |
[[ $channel ]] && { print "\n${c[r]}🛑 ERROR: Failed to find an upgrade path for release in requested channel!${c[z]}\n ${c[c]}Try omitting -c/--chan or selecting a different major release${c[z]}"; exit $rc; } | |
[[ $chan == $channels ]] && { print "\n${c[r]}🛑 ERROR: Failed to find an upgrade path for release in any of the standard channels!${c[z]}\n ${c[c]}Try selecting a different major release${c[z]}"; exit $rc; } | |
continue | |
fi | |
done | |
[[ $gotgood ]] && rc=0 | |
} | |
# ██████╗ ███████╗ ██████╗ ██╗ ██╗██╗██████╗ ███████╗███████╗ | |
# ██╔══██╗██╔════╝██╔═══██╗██║ ██║██║██╔══██╗██╔════╝██╔════╝ | |
# ██████╔╝█████╗ ██║ ██║██║ ██║██║██████╔╝█████╗ ███████╗ | |
# ██╔══██╗██╔══╝ ██║▄▄ ██║██║ ██║██║██╔══██╗██╔══╝ ╚════██║ | |
# ██║ ██║███████╗╚██████╔╝╚██████╔╝██║██║ ██║███████╗███████║ | |
# ╚═╝ ╚═╝╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝ | |
[[ $# -eq 0 ]] && halp --short | |
jq_help=$(jq -h) | |
if [[ $? -gt 0 || ! $jq_help =~ "jq - commandline JSON processor" ]]; then | |
print "${c[R]}✘ Unexpected error running 'jq --help' command -- need jq from https://github.com/stedolan/jq/releases${c[z]}" | |
exit 127 | |
fi | |
getopt -T | |
if [[ $? != 4 ]]; then | |
print "${c[R]}✘ Missing GNU getopt command (Linux: usually comes in util-linux; Mac OS X: can install from MacPorts)${c[z]}" | |
exit 127 | |
fi | |
# █████╗ ██████╗ ██████╗ ███████╗ | |
# ██╔══██╗██╔══██╗██╔════╝ ██╔════╝ | |
# ███████║██████╔╝██║ ███╗███████╗ | |
# ██╔══██║██╔══██╗██║ ██║╚════██║ | |
# ██║ ██║██║ ██║╚██████╔╝███████║ | |
# ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝ | |
# Use getopt to validate args while allowing combined short-opts and long-opts with equals sign | |
getopt="getopt --name=$me -o $opts_short -l $opts_long" | |
if ! errs=$($getopt -- "$@" 2>&1 >/dev/null); then | |
errs=${errs//$me/✘ ERROR} | |
errs=${errs//invalid option -- \'/unrecognized option \'-} | |
if command -v sed >/dev/null; then | |
errs=$(sed -r "s|(option) (requires an argument) -- '(.)'|\1 '-\3' \2|" <<<${errs}) | |
else | |
errs=${errs//option requires an argument -- \'/option requires an argument: \'-} | |
fi | |
halp "$errs" | |
fi | |
args=$($getopt -- "$@") | |
eval set -- "$args" | |
until [[ $1 == -- ]]; do | |
case $1 in | |
-h) halp --mid | |
;; | |
--help) halp --long | |
;; | |
-i|--ignore-releasetxt) ignoreMissingReleaseTxt=1 | |
;; | |
-e|--allow-non-stable) allowNonStable=1 | |
;; | |
-c|--chan) shift; channel=$1 | |
[[ $channel == cand ]] && channel=candidate | |
[[ $channel =~ ^(eus|stable|fast|candidate|prerelease)$ ]] || halp "✘ ERROR: invalid CHANNEL argument supplied for '-c'" | |
;; | |
-a|--arch) shift; arch=$1 | |
;; | |
-v|--verbose) verbose=1 | |
;; | |
-u|--list-updates) listUpdates=1 | |
;; | |
-m|--desired-major) shift; desiredMajor=$1 | |
;; | |
*) halp "✘ ERROR: invalid option '$1'" | |
esac | |
shift | |
done | |
shift | |
[[ $# == 0 ]] && halp "✘ ERROR: required argument (X.Y.Z or X.Y) is missing" | |
# Parse remaining non-option args | |
while [[ $# -gt 0 ]]; do | |
# $1 is X.Y.Z | |
if [[ $1 =~ ^4\.[0-9]+\.[0-9]+([-.].+)?$ ]]; then | |
[[ $release ]] && halp "✘ ERROR: cannot specify X.Y.Z more than once" | |
releaseMajor=$(cut -d. -f1-2 <<<$1) | |
releaseMinor=$(cut -d. -f3- <<<$1) | |
release=$1 | |
# $1 is X.Y | |
elif [[ $1 =~ ^4\.[0-9]+$ ]]; then | |
[[ $major ]] && halp "✘ ERROR: cannot specify X.Y more than once" | |
major=$1 | |
else | |
halp "✘ ERROR: invalid args ('$1') -- expecting nothing more than X.Y.Z and/or X.Y" | |
fi | |
shift | |
continue | |
done | |
if [[ $major ]]; then | |
[[ $release ]] && opMode=3 || opMode=2 | |
else | |
opMode=1 | |
fi | |
[[ $channel && $opMode == 1 ]] && halp "✘ ERROR: improper use of -c/--chan option; only has meaning with X.Y" | |
[[ $allowNonStable && $opMode != 1 ]] && halp "✘ ERROR: improper use of -e/--allow-non-stable option with X.Y; only has meaning in X.Y.Z mode" | |
# Now set a default for channel in mode1 & mode2 | |
[[ $opMode != 3 && -z $channel ]] && channel=stable | |
# Clear arch if it's set to amd64; otherwise add a space | |
if [[ $arch == amd64 ]]; then | |
arch= | |
elif [[ $arch ]]; then | |
arch+=" " | |
fi | |
# ███╗ ███╗ █████╗ ██╗███╗ ██╗ | |
# ████╗ ████║██╔══██╗██║████╗ ██║ | |
# ██╔████╔██║███████║██║██╔██╗ ██║ | |
# ██║╚██╔╝██║██╔══██║██║██║╚██╗██║ | |
# ██║ ╚═╝ ██║██║ ██║██║██║ ╚████║ | |
# ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ | |
case $opMode in | |
1) main_mode1 ;; | |
2) main_mode2 ;; | |
# Don't care about releasetxt in mode3 | |
3) main_mode3; releasetxtPublished=1 ;; | |
esac | |
# If release.txt is missing and -i/--ignore-releasetxt wasn't used... | |
[[ $releasetxtPublished ]] || exit ${RCs[release_has_no_releasetxt]} | |
# Otherwise, we just exit with $rc which was set above | |
exit $rc |
Random update: I just started running this on a Fedora35 system which had a jq
rpm installed, so I didn't need to download jq manually like I have in the past (from github.com/stedolan/jq/releases). However, I was horrified to find that jq's sorting wasn't working correctly. See:
That's with:
$ rpm -q jq
jq-1.6-10.fc35.x86_64
$ jq -V
jq-1.6
So I went and grabbed the binary direct from the latest (Nov 01, 2018) release on github and put that in my ~/bin/
and now all is well with jq's sorting:
So let this serve as a warning against using distro-provided packages. Sigh.
Pushed a minor cosmetic update that has no functional changes -- I only changed the usage/help pages to (hopefully) improve clarity, replacing all instances of OCP_VERSION
with simply 4.x.y
and replacing all instances of OCP_MAJOR_VERSION
with 4.x
. It's an improvement.
Massive update. This should really be in a repo, but oh well not today. New screenshots follow.
Three different operating modes, depending on what args you pass
(screenshot updated 2022-11-11; minor cosmetic change)
Mode 1: Determine highest channel in which 4.x.y can be found
(The original sole behavior -- prior to massive update.)
Verbosity is fun
Mode 2: Determine newest release of 4.x found in a channel
Verbosity
Mode 3: Determine available upgrade paths from starting 4.x.y to desired 4.x
Verbosity
Full help page
(screenshot updated 2022-11-11; minor cosmetic change)