Skip to content

Instantly share code, notes, and snippets.

@ryran
Last active February 10, 2023 00:47
Show Gist options
  • Save ryran/072409b1b7efd5018683a8c45e019652 to your computer and use it in GitHub Desktop.
Save ryran/072409b1b7efd5018683a8c45e019652 to your computer and use it in GitHub Desktop.
OCP4: leverage api.openshift.com/api/upgrades_info to inspect OCP versions
#!/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 <rsaw@redhat.com>
#
# 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
@ryran
Copy link
Author

ryran commented Jan 24, 2020

ocp4-chk-upgrade-channel

@ryran
Copy link
Author

ryran commented Jan 24, 2020

PS: If you like this, you may also like the more comprehensive ocp4-download-clients, which offers a superset of this script's behavior.

EDIT, a year later: Note that ocp4-download-clients no longer offers a superset of this script's abilities.

@ryran
Copy link
Author

ryran commented Mar 8, 2021

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)
ocuc-usage

Mode 1: Determine highest channel in which 4.x.y can be found

(The original sole behavior -- prior to massive update.)
ocuc-minor

Verbosity is fun

ocuc-minor-verbose

Mode 2: Determine newest release of 4.x found in a channel

ocuc-major

Verbosity

ocuc-major-verbose

Mode 3: Determine available upgrade paths from starting 4.x.y to desired 4.x

ocuc-upgrades

Verbosity

ocuc-upgrades-verbose

Full help page

(screenshot updated 2022-11-11; minor cosmetic change)
ocuc-h

@ryran
Copy link
Author

ryran commented Jan 18, 2022

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:
mannnnn

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:
bs

So let this serve as a warning against using distro-provided packages. Sigh.

@ryran
Copy link
Author

ryran commented Nov 12, 2022

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment