Skip to content

Instantly share code, notes, and snippets.

@alekonko
Forked from ryran/ocp4-download-clients
Created November 7, 2020 00:10
Show Gist options
  • Select an option

  • Save alekonko/623a49d042faff873378d399ca7a2bc8 to your computer and use it in GitHub Desktop.

Select an option

Save alekonko/623a49d042faff873378d399ca7a2bc8 to your computer and use it in GitHub Desktop.

Revisions

  1. @ryran ryran created this gist Jan 24, 2020.
    403 changes: 403 additions & 0 deletions ocp4-download-clients
    Original file line number Diff line number Diff line change
    @@ -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