-
-
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 |
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.
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
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.





