|
|
@@ -0,0 +1,403 @@ |
|
|
#!/bin/bash |
|
|
# ocp4-download-clients v0.2.0 last mod 2020/01/23 |
|
|
# Copyright 2020 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. |
|
|
|
|
|
|
|
|
# ██ ██ █████ ██ ██████ |
|
|
# ██ ██ ██ ██ ██ ██ ██ |
|
|
# ███████ ███████ ██ ██████ |
|
|
# ██ ██ ██ ██ ██ ██ |
|
|
# ██ ██ ██ ██ ███████ ██ |
|
|
|
|
|
|
|
|
version=$1 |
|
|
|
|
|
if [[ $version =~ ^4\.[0-9]+\.[0-9]+$ ]]; then |
|
|
major=${1%.*} |
|
|
minor=${1##*.} |
|
|
else |
|
|
cat <<-EOF |
|
|
usage: ${0##*/} OCP_VERSION |
|
|
Download openshift-install & openshift-client tarballs for OCP_VERSION |
|
|
OCP_VERSION should be something like "4.1.15" or "4.2.2" |
|
|
|
|
|
Tasks: |
|
|
|
|
|
• Figures out the highest upgrade channel in which OCP_VERSION can be found |
|
|
Ordering: "stable", "fast", "candidate", "prerelease" |
|
|
|
|
|
• Checks whether the release-provided errata advisory URL has been published |
|
|
|
|
|
• Checks whether the Quay release image is available |
|
|
|
|
|
• Uses RH PGP key to validate sha256sum.txt.sig |
|
|
|
|
|
• Downloads openshift-{install,client} tarballs if not already in CWD |
|
|
|
|
|
• Validates file checksums against those in sha256sum.txt.sig |
|
|
|
|
|
• Retries downloads in a loop (up to a point) if any of that fails |
|
|
|
|
|
Requires: |
|
|
• curl |
|
|
• gpg |
|
|
• sed, gawk |
|
|
• yq (https://github.com/mikefarah/yq/releases) |
|
|
• jq (https://github.com/stedolan/jq/releases) |
|
|
|
|
|
Set NO_COLORS=1 to disable ANSI escape code colors |
|
|
EOF |
|
|
exit 1 |
|
|
fi |
|
|
|
|
|
|
|
|
# ███████ ███████ ████████ ██ ██ ██████ |
|
|
# ██ ██ ██ ██ ██ ██ ██ |
|
|
# ███████ █████ ██ ██ ██ ██████ |
|
|
# ██ ██ ██ ██ ██ ██ |
|
|
# ███████ ███████ ██ ██████ ██ |
|
|
|
|
|
|
|
|
# Resources |
|
|
url=https://mirror.openshift.com/pub/openshift-v4/clients/ocp/$version |
|
|
release=$version-release.txt |
|
|
client=openshift-client-linux-$version.tar.gz |
|
|
install=openshift-install-linux-$version.tar.gz |
|
|
digest=$version-sha256sum.txt.sig |
|
|
# They changed things up starting with 4.1.30 and 4.2.14 |
|
|
if [[ $major == 4.1 && $minor -lt 30 ]] || [[ $major == 4.2 && $minor -lt 14 ]]; then |
|
|
releaseTag=$version |
|
|
else |
|
|
releaseTag=$version-x86_64 |
|
|
fi |
|
|
releaseImage=https://quay.io/v2/openshift-release-dev/ocp-release/manifests/$releaseTag |
|
|
|
|
|
|
|
|
# Colors |
|
|
[[ ${NO_COLORS} ]] || declare -A c=([z]='\033[0;0m' [Q]='\033[0;0m\033[1;1m' [r]='\033[0;31m' [R]='\033[1;31m' [g]='\033[0;32m' [G]='\033[1;32m' [y]='\033[0;33m' [Y]='\033[1;33m' [b]='\033[0;34m' [B]='\033[1;34m' [m]='\033[0;35m' [M]='\033[1;35m' [c]='\033[0;36m' [C]='\033[1;36m') |
|
|
|
|
|
print() { |
|
|
local echo_opts= |
|
|
while [[ $1 =~ ^- ]]; do |
|
|
echo_opts+="$1 " |
|
|
shift |
|
|
done |
|
|
echo -e $echo_opts "$@" |
|
|
} |
|
|
|
|
|
validate_url() { |
|
|
local errs |
|
|
errs=$(curl --fail -o /dev/null -LSsI $1 2>&1) && return |
|
|
print "$indent${c[R]}✘ Failed to get $1${c[z]}${c[r]}\n$indent $errs${c[z]}" |
|
|
return 2 |
|
|
} |
|
|
|
|
|
download_file() { |
|
|
local remote=$1 dest=$2 toDest= try=1 |
|
|
[[ $dest ]] && toDest=" to ./$dest" || dest=$1 |
|
|
print "\n\t${c[Y]}Downloading $url/$remote$toDest ...${c[z]}" |
|
|
print "${c[b]}" >&2 |
|
|
until (( try == 3 )); do |
|
|
if curl --fail -Lo "$dest" "$url/$remote"; then |
|
|
print -n "${c[z]}" >&2 |
|
|
return |
|
|
else |
|
|
print "\n\t${c[r]} ✘ Failure trying to get this file (attempt #$try)${c[z]}" |
|
|
print "${c[b]}" >&2 |
|
|
fi |
|
|
((try++)) |
|
|
done |
|
|
print "\n\t${c[R]}✘ Giving up on getting this file${c[z]}" |
|
|
exit 2 |
|
|
} |
|
|
|
|
|
|
|
|
# ██████ ██████ ███████ ██████ ███████ ██████ ███████ |
|
|
# ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ |
|
|
# ██████ ██████ █████ █████ ██████ █████ ██ ██ ███████ |
|
|
# ██ ██ ██ ██ ██ ██ ██ ██ ▄▄ ██ ██ |
|
|
# ██ ██ ██ ███████ ██ ██ ███████ ██████ ███████ |
|
|
# ▀▀ |
|
|
|
|
|
|
|
|
for cmd in curl sed gawk; do |
|
|
if ! command -v $cmd >/dev/null; then |
|
|
print "${c[R]}✘ Missing '$cmd' command!${c[z]}" |
|
|
exit 1 |
|
|
fi |
|
|
done |
|
|
|
|
|
yq_help=$(yq -h) |
|
|
if [[ $? -gt 0 || ! $yq_help =~ "yq is a lightweight and portable command-line YAML processor" ]]; then |
|
|
print "${c[R]}✘ Unexpected error running 'yq --help' command -- need yq from https://github.com/mikefarah/yq/releases${c[z]}" |
|
|
exit 1 |
|
|
fi |
|
|
|
|
|
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 1 |
|
|
fi |
|
|
|
|
|
if command -v gpg >/dev/null; then |
|
|
gpg=gpg |
|
|
elif command -v gpg2 >/dev/null; then |
|
|
gpg=gpg2 |
|
|
else |
|
|
print "${c[R]}✘ Missing 'gpg'/'gpg2' command! ... !?!?${c[z]}" |
|
|
exit 1 |
|
|
fi |
|
|
|
|
|
gpg_vers=$($gpg --version) |
|
|
if [[ $? -gt 0 || $gpg_version =~ GnuPG ]]; then |
|
|
print "${c[R]}✘ Unexpected error running '$gpg --version' command ... !?!?${c[z]}" |
|
|
exit 1 |
|
|
fi |
|
|
|
|
|
|
|
|
# ██████ ██████ ██████ ██ ██ ███████ ██ ██ ███████ |
|
|
# ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ |
|
|
# ██ ███ ██████ ██ ███ █████ █████ ████ ███████ |
|
|
# ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ |
|
|
# ██████ ██ ██████ ██ ██ ███████ ██ ███████ |
|
|
|
|
|
|
|
|
# Ensure we have release key for verification |
|
|
keyid=FD431D51 |
|
|
if ! $gpg -k $keyid &>/dev/null; then |
|
|
# Try to import it from RHEL location |
|
|
key=/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release |
|
|
[[ -r $key ]] && $gpg --import $key |
|
|
fi |
|
|
# If still don't have it, try to download it |
|
|
if ! $gpg -k $keyid &>/dev/null; then |
|
|
key=${keyid,,}.txt |
|
|
if ! curl -LSsO https://www.redhat.com/security/data/$key; then |
|
|
print "${c[R]}✘ Failed to get Red Hat $keyid release key (see https://access.redhat.com/security/team/key)${c[z]}" |
|
|
exit 1 |
|
|
fi |
|
|
$gpg --import $key |
|
|
fi |
|
|
# If still don't have it, abort |
|
|
if ! $gpg -k $keyid &>/dev/null; then |
|
|
print "${c[R]}✘ Problem with gpg -- still can't see $keyid key in public keyring${c[z]}" |
|
|
exit 1 |
|
|
fi |
|
|
|
|
|
|
|
|
# ██████ ██ ██ ██ ██ ██████ ███████ ██ ███████ █████ ███████ ███████ |
|
|
# ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ |
|
|
# ██ ███████ █████ ██████ █████ ██ █████ ███████ ███████ █████ |
|
|
# ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ |
|
|
# ██████ ██ ██ ██ ██ ██ ██ ███████ ███████ ███████ ██ ██ ███████ ███████ |
|
|
|
|
|
|
|
|
# Only do this stuff if we don't have ./$version-release.txt |
|
|
if ! [[ -s $release ]]; then |
|
|
# Make sure release exists |
|
|
validate_url $url/release.txt || exit |
|
|
|
|
|
# Grab release.txt and remove the non-yaml stuff at the beginning |
|
|
curl -LSs $url/release.txt | sed -n '/^---$/,$p' >$release |
|
|
fi |
|
|
|
|
|
# If yq read fails, we have a problem |
|
|
if ! images=$(yq read $release Images); then |
|
|
print "${c[R]}✘ Unexpected error running 'yq read' -- need yq from https://github.com/mikefarah/yq/releases${c[z]}" |
|
|
exit 1 |
|
|
fi |
|
|
|
|
|
# If there is no "Images" attr, we have a problem |
|
|
if [[ $images == null ]]; then |
|
|
print "${c[R]}✘ Couldn't find yaml 'Images' attr in $url/$release${c[z]}" |
|
|
exit 3 |
|
|
fi |
|
|
|
|
|
# Remove 'Images' attr |
|
|
releaseInfo=$(yq delete $release Images) |
|
|
|
|
|
# Fix bad yaml -- duplicate Metadata attr |
|
|
if [[ $(yq read - 'Release Metadata' <<<"${releaseInfo}" | grep ^Metadata: | wc -l) == 2 ]]; then |
|
|
releaseInfo=$(gawk '!/^ Metadata:$/ || ++ctr != 2' <<<"${releaseInfo}") |
|
|
fi |
|
|
|
|
|
# Get errata |
|
|
errataUrl=$(yq read - 'Release Metadata.Metadata.url' <<<"${releaseInfo}") |
|
|
|
|
|
# Check upgrade channel |
|
|
for channel in stable fast candidate prerelease x; do |
|
|
out=$(curl -sSH "Accept: application/json" "https://api.openshift.com/api/upgrades_info/v1/graph?channel=${channel}-${major}&arch=amd64" | jq -er --arg version $version '.nodes[] | select(.version == $version) | .version') |
|
|
[[ $? == 0 && $out == $version ]] && break |
|
|
done |
|
|
|
|
|
case $channel in |
|
|
x) |
|
|
channelMsg="${c[R]} ☢️ ☣️ U N K N O W N ☣️ ☢️ \n${c[r]} WARNING: Could not find release in ANY upgrade channel!" ;; |
|
|
stable) |
|
|
channelMsg="${c[G]}✔✔✔ $channel ✔✔✔" ;; |
|
|
fast) |
|
|
channelMsg="${c[Y]}⚠️ ⚠️ $channel ⚠️ ⚠️ \n${c[m]} WARNING: Release has not (yet) been tagged into the stable channel" ;; |
|
|
candidate|prerelease) |
|
|
channelMsg="${c[R]}⛔ ⛔ $channel ⛔ ⛔ \n${c[m]} WARNING: Release has not (yet) been tagged into the stable channel" ;; |
|
|
esac |
|
|
|
|
|
# Print release details |
|
|
print "\n${c[Q]}Red Hat OpenShift Container Platform v$major release $minor${c[z]}" |
|
|
print "${c[Q]}--------------------------------------------------------------------------------${c[z]}" |
|
|
print "\nRelease source: $url${c[z]}" |
|
|
print "\nRelease upgrade channel: $channelMsg${c[z]}" |
|
|
print "\n${c[b]} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ${c[z]}" >&2 |
|
|
print "${c[b]}release.txt info:" >&2 |
|
|
sed 's/^/ /' <<<"$releaseInfo" >&2 |
|
|
print "${c[b]} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ${c[z]}" >&2 |
|
|
|
|
|
# Make sure errata advisory exists |
|
|
print "\n${c[Q]}• Release-provided errata URL${c[z]}" |
|
|
print "\n\t${c[Y]}Fetching ...${c[z]}" |
|
|
if indent='\t ' validate_url $errataUrl; then |
|
|
print "\t ${c[G]}✔ GOOD - Errata advisory ($errataUrl) has been published!${c[z]}" |
|
|
else |
|
|
print "\n\t ${c[m]}WARNING: The fact that the errata advisory URL provided by the\n\t release.txt is unreachable could signal that this version of\n\t OCP is very new${c[z]}" |
|
|
fi |
|
|
|
|
|
# Make sure release manifest exists |
|
|
print "\n${c[Q]}• Quay release image URL${c[z]}" |
|
|
print "\n\t${c[Y]}Fetching ...${c[z]}" |
|
|
if indent='\t ' validate_url $releaseImage; then |
|
|
print "\t ${c[G]}✔ GOOD - Quay release image ($releaseImage) is available!${c[z]}" |
|
|
else |
|
|
print "\n\t ${c[m]}WARNING: The fact that the Quay release image is unavailable\n\t will likely prevent you from performing a successful install${c[z]}" |
|
|
fi |
|
|
|
|
|
|
|
|
# ███████ ██ ██ █████ ██████ ███████ ██████ ███████ ██ ██ ███ ███ |
|
|
# ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ████ |
|
|
# ███████ ███████ ███████ █████ ███████ ███████ ███████ ██ ██ ██ ████ ██ |
|
|
# ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ |
|
|
# ███████ ██ ██ ██ ██ ███████ ███████ ██████ ███████ ██████ ██ ██ |
|
|
|
|
|
|
|
|
# Download shasum file |
|
|
print "\n${c[Q]}• $digest${c[z]}" |
|
|
|
|
|
attempt=0 maxtries=4 |
|
|
|
|
|
while :; do |
|
|
|
|
|
((attempt++)) |
|
|
if ((attempt > maxtries)); then |
|
|
print "\n\t${c[R]}✘ Giving up on getting this file after $maxtries attempts${c[z]}" |
|
|
exit 3 |
|
|
fi |
|
|
|
|
|
if [[ -s $digest ]]; then |
|
|
print "\n\t${c[g]}Already downloaded${c[z]}" |
|
|
else |
|
|
download_file sha256sum.txt.sig $digest |
|
|
fi |
|
|
|
|
|
print "\n\t${c[Y]}Validating ...${c[z]}" |
|
|
|
|
|
# Verify shasum file with gpg |
|
|
verify=$($gpg --trust-model always --verify $digest 2>&1) |
|
|
if (($? == 0)); then |
|
|
print "\t ${c[G]}✔ GOOD - embedded signature verified!${c[z]}" |
|
|
print "\t ${c[g]}$(grep 'Good signature from' <<<"$verify")${c[z]}" |
|
|
# We're done |
|
|
break |
|
|
else |
|
|
print "\t ${c[R]}✘ Unable to verify integrity of digest file $digest${c[z]}" |
|
|
print "${c[r]}$(sed 's/^/\t /' <<<"$verify")${c[z]}" |
|
|
# Let's try again |
|
|
if (( attempt < maxtries )); then |
|
|
print "\n\t${c[M]}(Attempting to re-download)${c[z]}" |
|
|
# Let's try again |
|
|
rm -f $digest |
|
|
fi |
|
|
fi |
|
|
|
|
|
done |
|
|
|
|
|
# Let's parse it one more time to extract the shasums |
|
|
shasums=$($gpg --trust-model always -d $digest 2>/dev/null) |
|
|
|
|
|
|
|
|
# ██████ ██████ ██ ██ ███ ██ ██ ██████ █████ ██████ |
|
|
# ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ |
|
|
# ██ ██ ██ ██ ██ █ ██ ██ ██ ██ ██ ██ ██ ███████ ██ ██ |
|
|
# ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ |
|
|
# ██████ ██████ ███ ███ ██ ████ ███████ ██████ ██ ██ ██████ |
|
|
|
|
|
|
|
|
rc=0 |
|
|
|
|
|
for f in $client $install; do |
|
|
|
|
|
print "\n${c[Q]}• $f${c[z]}" |
|
|
|
|
|
attempt=0 maxtries=4 |
|
|
|
|
|
while :; do |
|
|
|
|
|
((attempt++)) |
|
|
if ((attempt > maxtries)); then |
|
|
print "\n\t${c[R]}✘ Giving up on getting this file after $maxtries attempts${c[z]}" |
|
|
rc=3 |
|
|
# Move on to next file |
|
|
break |
|
|
fi |
|
|
|
|
|
if [[ -s $f ]]; then |
|
|
print "\n\t${c[g]}Already downloaded${c[z]}" |
|
|
else |
|
|
download_file $f |
|
|
fi |
|
|
|
|
|
print "\n\t${c[Y]}Checksumming ...${c[z]}" |
|
|
|
|
|
if ! expected=$(grep $f <<<"$shasums"); then |
|
|
print "\t ${c[R]}✘ Digest file $digest didn't contain file '$f'${c[z]}" |
|
|
rc=3 |
|
|
# Move on to next file |
|
|
break |
|
|
fi |
|
|
|
|
|
if ! actual=$(sha256sum $f 2>&1); then |
|
|
print "\t ${c[R]}✘ Error running sha256sum against file '$f'\n\t $actual${c[z]}" |
|
|
if (( attempt < maxtries )); then |
|
|
print "\n\t${c[M]}(Attempting to re-download)${c[z]}" |
|
|
# Let's try again |
|
|
rm -f $f |
|
|
fi |
|
|
continue |
|
|
fi |
|
|
|
|
|
if [[ $expected == $actual ]]; then |
|
|
print "\t ${c[G]}✔ GOOD - checksums match!${c[z]}" |
|
|
break |
|
|
else |
|
|
print "\t ${c[R]}✘ CHECKSUM MISMATCH! LIKELY CORRUPTED FILE!${c[z]}" |
|
|
print "\t ${c[g]}$expected${c[z]} (expected)" |
|
|
print "\t ${c[r]}$actual${c[z]} (actual)" |
|
|
if (( attempt < maxtries )); then |
|
|
print "\n\t${c[M]}(Attempting to re-download)${c[z]}" |
|
|
# Let's try again |
|
|
rm -f $f |
|
|
fi |
|
|
continue |
|
|
fi |
|
|
|
|
|
done |
|
|
done |
|
|
|
|
|
exit $rc |