#!/usr/bin/env bash
#
# Mirantis Container Runtime installer
#
# Script build information:
# COMMIT_SHA=cfea8c511a0f69374c432f152b0bac6c406d6cf0
# COMMIT_SHA_PVAL=cfea8c5
# SEMVER_VERSION=1.0.24
# PUBLISH_STRING=stable


# ENV VARIABLES:
# Essential:
# - DOCKER_URL - URL to package repo
# - CHANNEL - channel in the package repo
# - VERSION - MCR release version
# Optional:
# - CONTAINERD_VERSION- Containerd package version
# Development:
# - DRY_RUN - installation will be with dry-run flag
# - SKIP_REPO_SETUP - the current configured repos will be only used
#                     DOCKER_URL and CHANNEL will be ignored
#

set -e

MCR_MAIN_VERSION=""
DIST_ID=""


DOCKER_PACKAGE_NAME="docker-ee"
DOCKER_CLI_PACKAGE_NAME="${DOCKER_PACKAGE_NAME}-cli"
DOCKER_ROOTLESS_PACKAGE_NAME="${DOCKER_PACKAGE_NAME}-rootless-extras"
CONTAINERD_PACKAGE_NAME="containerd.io"
CRI_DOCKERD_PACKAGE_NAME="cri-dockerd-ee"
RUNC_PACKAGE_NAME="runc-ee"

MIN_ROOTLESS_VER="20.10.12"
MIN_MCR_WITH_C8D_VER="23.0.1"

SH_C='sh -c'

# Parse an MCR product version string.
#
# If the version is well-formed, this function exits with a success status
# and the parsed components of the version are placed into $BASH_REMATCH.
#
# ${BASH_REMATCH[1]} - the MAJOR.MINOR.PATCH
# ${BASH_REMATCH[3]} - the tp or rc designator
# ${BASH_REMATCH[4]} - the pre-release increment or hotfix number
#
# Consider the following versions that we might support:
# 23.0.12     - A version by itself. Install latest release build.
# 23.0.12-0   - A specific build version. We use these, for example, if the
# 23.0.12-1     we need to rebuild with a new fipster.
# 23.0.13-tp1 - A specific tp version
# 23.0.13-tp2
# 23.0.13-rc1 - A specific rc version
# 23.0.13-rc2
#
function mcr_ver() {
    [[ $1 =~ ^([0-9]+\.[0-9]+\.[0-9]+)(-([1-9][0-9]*))?(-(tp|rc)([1-9][0-9]*))?$ ]]
}

get_dep_package_version_deb(){
    local package="${1:?Please specify package name}"
    local version="${2:?Please specify package version}"
    local dep_package="${3:?Please specify dependance package name}"

    apt-cache show "${package}=${version}" \
                | grep -oP "${dep_package}"' \([^ ]+ \K[^\)]+'
}

get_dep_package_version_yum(){
    local package="${1:?Please specify package name}"
    local version="${2:?Please specify package version}"
    local dep_pkg="${3:?Please specify dependance package name}"

    ${SH_C} "yum deplist ${package}-${version}" \
            | awk -v dep_pkg="${dep_pkg}" '$1 == "dependency:" && $2 == dep_pkg {print $4}'
}

get_dep_package_version_zypper(){
    local package="${1:?Please specify package name}"
    local version="${2:?Please specify package version}"
    local dep_package="${3:?Please specify dependance package name}"

    local _full_package_version
    _full_package_version="$(get_full_package_version_zypper "${package}" "${version}")"

    # Unfortunately, zypper doesn't support `info` command for specific package version
    # so let's use dirty hack by downloading the rpm file and find out the dependency
    ${SH_C} "zypper install --force --download-only --no-confirm ${package}-${_full_package_version}" &> /dev/null
    local _file_package_version
    _file_package_version="${_full_package_version##*:}"
    find /var/cache/zypp/packages/ -name "${package}-${_file_package_version}"'*.rpm' -exec rpm -qR -p {} \; \
        | awk '$1 == "'"${dep_package}"'" {print $3}'
}

get_dep_package_version(){
    local package="${1:?Please specify package name}"
    local version="${2:?Please specify package version}"
    local dep_package="${3:?Please specify dependance package name}"

    case "${DIST_ID}" in
    ubuntu)
        get_dep_package_version_deb "${package}" "${version}" "${dep_package}"
        ;;
    centos | rhel | rocky | amzn | ol)
        get_dep_package_version_yum "${package}" "${version}" "${dep_package}"
        ;;
    sles | opensuse-leap)
        get_dep_package_version_zypper "${package}" "${version}" "${dep_package}"
        ;;
    esac
}

function get_package_epoch() {
    local package="${1:?Please specify package name}"
    local dist_id="${2:?Please specify dist_id}"

    case "${dist_id}" in
    ubuntu)
        declare -A _deb_package_epoch_map=(
            ["docker-ee"]="5:"
            ["docker-ee-cli"]="5:"
            ["docker-ee-rootless-extras"]="5:"
            ["containerd.io"]=""
        )
        echo "${_deb_package_epoch_map[${package}]}"
        ;;
    esac
}

get_full_package_version_deb () {
    local package="${1:?Please specify package name}"
    local version="${2:?Please specify package version}"

    local _epoch
    _epoch="$(get_package_epoch "${package}" "${DIST_ID}")"
    apt-cache show "${package}=${_epoch}${version}"'*' \
                | awk '$1 == "Version:" {print $2}'
}

get_full_package_version_yum () {
    local package="${1:?Please specify package name}"
    local version="${2:?Please specify package version}"

    local _repoquery=repoquery
    command_exists dnf && _repoquery="dnf repoquery"
    local _full_version
    _full_version="$( ${_repoquery} -q --repoid=docker-ee-"${CHANNEL}" --qf='%{evr}' "${package}-${version}"'*' )"
    echo "${_full_version#*:}"
}

get_full_package_version_zypper () {
    local package="${1:?Please specify package name}"
    local version="${2:?Please specify package version}"

    # Unfortunately, zypper doesn't support `info` command for specific package version
    # so let's use `zypper search` command which returns a sorted list of versions
    #S | Name      | Type    | Version                 | Arch   | Repository
    #--+-----------+---------+-------------------------+--------+---------------
    #  | docker-ee | package | 3:23.0.15-3             | x86_64 | docker-ee-test
    #  | docker-ee | package | 3:23.0.15-2.1.rc1       | x86_64 | docker-ee-test
    #  | docker-ee | package | 3:23.0.15-0.1.tp1       | x86_64 | docker-ee-test
    ${SH_C} "zypper search --match-exact --details --type package ${package}" \
        | gawk -v version="${version##*:}" 'index(gensub(/^[[:digit:]]+:/, "", 1, $4), version) == 1 {print $4; exit}' FS='[[:space:]]+\\|[[:space:]]+'
}

get_full_package_version() {
    local package="${1:?Please specify package name}"
    local version="${2:?Please specify package version}"

    case "${DIST_ID}" in
    ubuntu)
        get_full_package_version_deb "${package}" "${version}"
        ;;
    centos | rhel | rocky | amzn | ol)
        get_full_package_version_yum "${package}" "${version}"
        ;;
    sles | opensuse-leap)
        get_full_package_version_zypper "${package}" "${version}"
        ;;
    esac
}

get_docker_c8d_dep() {
    local version=${1:?"Please specify docker version"}
    local c8d_dep

    c8d_dep="$(get_dep_package_version "${DOCKER_PACKAGE_NAME}" "${version}" "${CONTAINERD_PACKAGE_NAME}")"
    get_full_package_version "${CONTAINERD_PACKAGE_NAME}" "${c8d_dep}"
}

get_c8d_mapped_version() {
    local mcr_ver=${1:-MCR_MAIN_VERSION}

    declare -rA _mcr_c8d_deb_map=(
        ["23.0.1"]="1.6.17"
        ["23.0.3"]="1.6.19"
        ["23.0.5"]="1.6.20"
        ["23.0.6"]="1.6.21"
        ["23.0.7"]="1.6.22"
        ["23.0.8"]="1.6.25~rc.1-1"
        ["23.0.9"]="1.6.28~rc.1-1"
        ["23.0.9-1"]="1.6.28~rc.1-2"
        ['23.0.10']="1.6.30~rc.2-1"
        ['23.0.13']="1.6.32"
        ['23.0.14']="1.6.33"
        )
    declare -rA _mcr_c8d_rpm_map=(
        ["23.0.1"]="1.6.17"
        ["23.0.3"]="1.6.19"
        ["23.0.5"]="1.6.20"
        ["23.0.6"]="1.6.21"
        ["23.0.7"]="1.6.22"
        ["23.0.8"]="1.6.25-2.1.rc.1.1"
        ["23.0.9"]="1.6.28-2.1.rc.1.1"
        ["23.0.9-1"]="1.6.28-3.1.rc.1.1"
        ["23.0.10"]="1.6.30-2.2.rc.2.1"
        ['23.0.13']="1.6.32"
        ['23.0.14']="1.6.33"
        )

    local _c8d_ver
    case "${DIST_ID}" in
    ubuntu)
        _c8d_ver="${_mcr_c8d_deb_map["${mcr_ver}"]}"
        ;;
    centos | rhel | rocky | amzn | ol | sles | opensuse-leap)
        _c8d_ver="${_mcr_c8d_rpm_map["${mcr_ver}"]}"
        ;;
    esac

    if [ -z "${_c8d_ver}" ]; then
        return 1
    fi

    echo "${_c8d_ver}"
}

get_docker_mapped_version() {
    local mcr_version="${1:-MCR_MAIN_VERSION}"

    declare -rA mcr_docker_deb_map=(
        ["23.0.9"]="23.0.9~3-"
        ["23.0.9-1"]="23.0.9~4"
        )
    declare -rA mcr_docker_rpm_map=(
        ["23.0.9"]="23.0.9-3"
        ["23.0.9-1"]="23.0.9-4"
        )

    local _docker_ver
    case "$DIST_ID" in
    ubuntu)
        _docker_ver=${mcr_docker_deb_map[${mcr_version}]}
        ;;
    centos | rhel | rocky | amzn | ol | sles | opensuse-leap)
        _docker_ver=${mcr_docker_rpm_map[${mcr_version}]}
        ;;
    esac

    if [ -n "${_docker_ver}" ]; then
        echo "${_docker_ver}"
    fi
}

get_docker_package_version() {
    local mcr_version="${1:-MCR_MAIN_VERSION}"

    mcr_ver "${mcr_version}"

    local _main_version=${BASH_REMATCH[1]}
    local _prerelease_designator=${BASH_REMATCH[5]}
    local _build_version=${BASH_REMATCH[6]}
    local _prerelease="${_prerelease_designator}${_build_version}"

    local _ver
    _ver="$(get_docker_mapped_version "${mcr_version}")"
    if [ -n "${_ver}" ]; then
        echo "${_ver}"
        return 0
    fi

    case "${DIST_ID}" in
    ubuntu)
        # the first number after the tilde corresponds to the prerelease
        # designator. for tp, it's 0, for rc, it's 2, and for release, it's 3.

        # build_version does not appear in prerelease versions, we ignore it.
        case "${_prerelease_designator}" in
        tp)
            echo "${_main_version}~0.${_build_version}.${_prerelease}"
            ;;
        rc)
            echo "${_main_version}~2.${_build_version}.${_prerelease}"
            ;;
        *)
            echo "${_main_version}~$(( 3 + _build_version ))"
            ;;
        esac
        ;;
    centos | rhel | rocky | amzn | ol)
        case "${_prerelease_designator}" in
        tp)
            echo "${_main_version}-0.${_build_version}.${_prerelease}."
            ;;
        rc)
            echo "${_main_version}-2.${_build_version}.${_prerelease}."
            ;;
        *)
            echo "${_main_version}-$(( 3 + _build_version ))."
            ;;
        esac
        ;;
    sles | opensuse-leap)
        # On zypper-based systems version terminates before any extra fluffy
        # bits, which means we can't end our search string (the thing we're
        # making here) with a dot. This means that, on zipper, we might match
        # both build version 29 (which ends in -30) and build version 0 (which
        # ends in -3). If we ever have 30 build versions, we can fix this.
        case "${_prerelease_designator}" in
        tp)
            echo "${_main_version}-0.${_build_version}.${_prerelease}"
            ;;
        rc)
            echo "${_main_version}-2.${_build_version}.${_prerelease}"
            ;;
        *)
            echo "${_main_version}-$(( 3 + _build_version ))"
            ;;
        esac
        ;;
    esac
}

get_c8d_version() {
    local mcr_ver=${1:?Please specify MCR package version}
    local docker_ver=${2:?Please specify docker package version}

    if [ -n "${CONTAINERD_VERSION}" ]; then
        echo "${CONTAINERD_VERSION}"
        return 0
    fi

    get_c8d_mapped_version "${mcr_ver}" || get_docker_c8d_dep "${docker_ver}"
}

get_c8d_package_version() {
    local mcr_ver=${1:?Please specify MCR package version}
    local docker_ver=${2:?Please specify docker package version}

    get_full_package_version "${CONTAINERD_PACKAGE_NAME}" "$(get_c8d_version "${mcr_ver}" "${docker_ver}")"
}

command_exists() {
    command -v "$@" > /dev/null 2>&1
}

on_ec2() {
    [ -f /sys/hypervisor/uuid ] && [ "$(head -c 3 /sys/hypervisor/uuid)" == ec2 ]
}

strip_trailing_slash() {
    echo "${1/%\/}"
}

# version_gte checks if the version specified in $VERSION is at least
# the given CalVer (YY.MM) version. returns 0 (success) if $VERSION is either
# unset (=latest) or newer or equal than the specified version. Returns 1 (fail)
# otherwise.
#
# examples:
#
# VERSION=20.10
# version_gte 20.10 // 0 (success)
# version_gte 19.03 // 0 (success)
# version_gte 21.10 // 1 (fail)
version_gte() {
    if [ -z "$VERSION" ]; then
            return 0
    fi

    calver_compare "${VERSION%%-*}" "$1"
}

# calver_compare compares two CalVer (YY.MM.VER) version strings. returns 0 (success)
# if version A is newer or equal than version B, or 1 (fail) otherwise. Patch
# releases and pre-release (-alpha/-beta) are not taken into account
#
# examples:
#
# calver_compare 20.10.12 19.03 // 0 (success)
# calver_compare 20.10.12 20.10.12 // 0 (success)
# calver_compare 19.03.02 20.10.12 // 1 (fail)
calver_compare() (
    set +x

    yy_a="$(echo "$1" | cut -d'.' -f1)"
    yy_b="$(echo "$2" | cut -d'.' -f1)"
    if (( "$yy_a" < "$yy_b" )); then
        return 1
    fi
    if (( "$yy_a" > "$yy_b" )); then
        return 0
    fi
    mm_a="$(echo "$1" | cut -d'.' -f2)"
    mm_b="$(echo "$2" | cut -d'.' -f2)"
    if (( "${mm_a}" < "${mm_b}" )); then
        return 1
    fi
    ver_a="$(echo "$1" | cut -d'.' -f3)"
    ver_b="$(echo "$2" | cut -d'.' -f3)"
    if (( "$ver_a" < "$ver_b" )); then
        return 1
    fi

    return 0
)

ubuntu_prepare() {
    declare -rx DEBIAN_FRONTEND=noninteractive

    local _pre_reqs="apt-transport-https ca-certificates curl software-properties-common"
    if ! command -v gpg > /dev/null; then
        _pre_reqs="$_pre_reqs gnupg"
    fi

    (
        set -ex
        ${SH_C} "apt-get update -qq"
        ${SH_C} "apt-get install -y -qq $_pre_reqs" &>/dev/null
    )

    local _ubuntu_url
    _ubuntu_url=$(strip_trailing_slash "${DOCKER_URL}")

    # Check if we have a gpg (should be valid repo to use if it's there) before appending suffix
    if ! curl -fsSL "${_ubuntu_url}/gpg" >/dev/null; then
        # URL's may not be suffixed with ubuntu, let's make sure that they are
        if [[ ! "${_ubuntu_url}" =~ /ubuntu$ ]]; then
            _ubuntu_url="${_ubuntu_url}/ubuntu"
        fi
    fi

    local _arch
    _arch="$(dpkg --print-architecture)"

    # Grab this outside of the command to install, so it's not muddled
    local _release
    # shellcheck disable=SC1091
    _release="$(. /etc/os-release && echo "${VERSION_CODENAME}")"

    (
        set -ex
        curl -fsSL "${_ubuntu_url}"/gpg | ${SH_C} "apt-key add -qq -" >/dev/null
        ${SH_C} "add-apt-repository -y 'deb [arch=${_arch}] ${_ubuntu_url} ${_release} ${CHANNEL}'" >/dev/null
        ${SH_C} "apt-get update -qq" >/dev/null
    )
}

ubuntu_install() {
    declare -rx DEBIAN_FRONTEND=noninteractive

    local docker_version="${1:?Specify docker version}"

    if [[ -z "${SKIP_REPO_SETUP}" ]]; then
        ubuntu_prepare
    fi

    local _docker_package="${DOCKER_PACKAGE_NAME}"
    # By default, don't include a cli_package and rootless_package to install just let the package manager grab the topmost one
    local _cli_package="${DOCKER_CLI_PACKAGE_NAME}"
    local _rootless_package="${DOCKER_ROOTLESS_PACKAGE_NAME}"
    local _containerd_package="${CONTAINERD_PACKAGE_NAME}"

    local _docker_package_version
    _docker_package_version="$(get_full_package_version "${_docker_package}" "${docker_version}")"

    local _cli_package_version
    _cli_package_version="$(get_full_package_version "${_cli_package}" "${docker_version}")"

    local _rootless_package_version
    if version_gte "${MIN_ROOTLESS_VER}"; then
        _rootless_package_version="$(get_full_package_version "${_rootless_package}" "${docker_version}")"
    fi

    echo "INFO: Searching repository for Docker package VERSION '${docker_version}'"
    if [ -z "${_docker_package_version}" ]; then
        echo
        echo "ERROR: '${docker_version}' not found amongst apt-cache madison results"
        echo
        exit 1
    fi

    # If a cli package was found for the given version then include it in the installation
    if [ -n "${_cli_package_version}" ]; then
        _cli_package+="=${_cli_package_version}"
    fi

    # If a rootless package was found for the given version then include it in the installation
    if [ -n "$_rootless_package_version" ]; then
        _rootless_package+="=${_rootless_package_version}"
    fi

    _docker_package+="=${_docker_package_version}"

    if version_gte "$MIN_MCR_WITH_C8D_VER"; then
        local _c8d_version
        _c8d_version="$(get_c8d_package_version "${MCR_MAIN_VERSION}" "${_docker_package_version}")"
        if [ -n "${_c8d_version}" ]; then
            _containerd_package+="=${_c8d_version}"
        fi
    fi

    (
        local _apt_flags="-y -qq --allow-downgrades ${DRY_RUN:+ --dry-run}"

        set -ex
        ${SH_C} "apt-get install ${_apt_flags} ${_docker_package} ${_cli_package} ${_rootless_package} ${_containerd_package}"
    )
}

yum_prepare() {
    local dist_id="${1:?Specify Distribution Id}"
    local dist_version="${2:?Please specify Distribution version}"

    local _yum_url
    _yum_url=$(strip_trailing_slash "${DOCKER_URL}")
    (
        set -ex
        command_exists curl || ${SH_C} 'yum install -q -y curl'
    )
    # Check if we have a usable repo file before appending suffix
    if ! curl  --fail --silent --location --head "${_yum_url}/docker-ee.repo" >/dev/null; then
        if [[ ! "${_yum_url}" =~ /centos$|/rhel$|rocky$ ]]; then
            _yum_url="${_yum_url}/${dist_id}"
        fi
    fi

    case ${dist_id}:${dist_version} in
    oraclelinux:7*)
        # Enable "Oracle Linux 7 Server Add ons (x86_64)" repo for oraclelinux7
        (
            set -ex
            ${SH_C} 'yum-config-manager --enable ol7_addons'
        )
        ;;
    rhel:7*)
        local _extras_repo="rhel-7-server-extras-rpms"
        if on_ec2; then
            ${SH_C} "yum install -y rh-amazon-rhui-client"
            _extras_repo="rhel-7-server-rhui-extras-rpms"
        fi
        # We don't actually make packages for 7.1, but they can still use the 7 repository
        if [ "${dist_version}" = "7.1" ]; then
            dist_version="7"
        fi
        # Enable extras repo for rhel
        (
            set -ex
            ${SH_C} "yum-config-manager --enable ${_extras_repo}"
        )
        ;;
    esac
    (
        set -ex
        ${SH_C} "echo '${_yum_url}' > /etc/yum/vars/dockerurl"
        ${SH_C} "echo '${dist_version}' > /etc/yum/vars/dockerosversion"
        ${SH_C} "yum install -q -y yum-utils device-mapper-persistent-data lvm2"
        ${SH_C} "yum-config-manager --add-repo ${_yum_url}/docker-ee.repo"
        ${SH_C} "yum-config-manager --disable 'docker-ee-*'"
        ${SH_C} "yum-config-manager --enable 'docker-ee-${CHANNEL}'"
    )
}

yum_install() {
    local docker_version="${1:?Specify docker version}"
    local dist_id="${2:?Specify Distribution Id}"
    local dist_version="${3:?Please specify Distribution version}"

    if [[ -z "${SKIP_REPO_SETUP}" ]]; then
        yum_prepare "${dist_id}" "${dist_version}"
    fi

    local _docker_package="${DOCKER_PACKAGE_NAME}"
    # By default, don't include a cli_package and rootless_package to install just let the package manager grab the topmost one
    local _cli_package="${DOCKER_CLI_PACKAGE_NAME}"
    local _rootless_package=""
    local _containerd_package="${CONTAINERD_PACKAGE_NAME}"

    local _docker_package_version
    _docker_package_version="$(get_full_package_version "${_docker_package}" "${docker_version}")"

    local _cli_package_version
    _cli_package_version="$(get_full_package_version "${_cli_package}" "${docker_version}")"

    local _rootless_package_version
    if version_gte "$MIN_ROOTLESS_VER" && [ "${dist_id}:${dist_version}" != "oraclelinux:7" ]; then
        # like the CLI, rootless-extras packages don't start with 3:. They don't
        # start with anything, actually.
        _rootless_package="${DOCKER_ROOTLESS_PACKAGE_NAME}"
        _rootless_package_version="$(get_full_package_version "${_rootless_package}" "${docker_version}")"
    fi

    echo "INFO: Searching repository for Docker package VERSION '${docker_version}'"
    if [ -z "${_docker_package_version}" ]; then
        echo
        echo "ERROR: '${docker_version}' not found amongst yum list results"
        echo
        exit 1
    fi

    if [ -n "$_cli_package_version" ]; then
        _cli_package+="-${_cli_package_version}"
    fi
    if [ -n "$_rootless_package_version" ]; then
        _rootless_package+="-${_rootless_package_version}"
    fi

    _docker_package+="-${_docker_package_version}"

    local _yum_cmd="install"
    # Check if we're doing an upgrade / downgrade and the command accordingly
    echo "INFO: Checking to determine whether this should be an upgrade or downgrade"
    # If the package isn't really installed then don't try upgrade / downgrade
    if ! yum list installed "${DOCKER_PACKAGE_NAME}" >/dev/null; then
        _yum_cmd="install"
    # Exit codes when using --assumeno will give 0 if there would be an upgrade/downgrade, 1 if there is
    elif ! ${SH_C} "yum upgrade --assumeno ${_docker_package}"; then
        _yum_cmd="upgrade"
    elif ! ${SH_C} "yum downgrade --assumeno ${_docker_package}"; then
        _yum_cmd="downgrade"
    fi

    local _c8d_version
    echo "INFO: will use install command $_yum_cmd"
    if version_gte "$MIN_MCR_WITH_C8D_VER"; then
        _c8d_version="$(get_c8d_package_version "${MCR_MAIN_VERSION}" "${_docker_package_version}")"
        if [ -n "${_c8d_version}" ]; then
            _containerd_package+="-${_c8d_version}"
        fi
    fi

    (
        local _yum_flags="-q"

        if [ -n "${DRY_RUN}" ]; then
            _yum_flags+=" --assumeno"
        else
            _yum_flags+=" --assumeyes"
        fi

        set -ex
        ${SH_C} "yum ${_yum_cmd} ${_yum_flags} ${_docker_package} ${_cli_package} ${_rootless_package} ${_containerd_package}"
    )
}

REPO_VERSION=""

zypper_prepare() {
    local _arch
    _arch="$(uname -m)"

    (
        set -ex
        ${SH_C} "zypper install --no-confirm curl gawk"
    )

    local _zypper_url
    _zypper_url=$(strip_trailing_slash "${DOCKER_URL}")
    # No need to append sles if we already have a valid repo
    if ! curl -fsL "${_zypper_url}/docker-ee.repo" >/dev/null; then
        _zypper_url="${_zypper_url}/sles"
    fi
    (
        set -ex
        # SLES images have installed Docker CE by default, which conflicts w/ MCR, so it needs to be deleted
        ${SH_C} "zypper remove -y docker docker-engine docker-libnetwork runc containerd" || true
        ${SH_C} "zypper removerepo docker-ee-${CHANNEL}" # this will always return 0 even if repo alias not found
        ${SH_C} "zypper addrepo ${_zypper_url}/${REPO_VERSION}/${_arch}/${CHANNEL} docker-ee-${CHANNEL}"
        ${SH_C} "rpm --import '${_zypper_url}/gpg'"
        ${SH_C} "zypper refresh"
    )
}

zypper_install() {
    local docker_version="${1:?Specify docker version}"
    local dist_version="${2}"

    REPO_VERSION=15

    if [[ -z "${SKIP_REPO_SETUP}" ]]; then
        zypper_prepare
    fi

    local _docker_package="${DOCKER_PACKAGE_NAME}"
    # By default, don't include a cli_package and _rootless_package to install just let the package manager grab the topmost one
    local _cli_package="${DOCKER_CLI_PACKAGE_NAME}"
    local _rootless_package=""
    local _containerd_package="${CONTAINERD_PACKAGE_NAME}"

    local _docker_package_version
    _docker_package_version="$(get_full_package_version "${_docker_package}" "${docker_version}")"

    local _cli_package_version
    _cli_package_version="$(get_full_package_version "${_cli_package}" "${docker_version}")"

    local _rootless_package_version
    if version_gte "${MIN_ROOTLESS_VER}"; then
        _rootless_package="${DOCKER_ROOTLESS_PACKAGE_NAME}"
        _rootless_package_version="$(get_full_package_version "${_rootless_package}" "${docker_version}")"
    fi

    echo "INFO: Searching repository for VERSION '${docker_version}'"
    if [ -z "${_docker_package_version}" ]; then
        echo
        echo "ERROR: '${docker_version}' not found amongst zypper search results"
        echo
        exit 1
    fi

    # If a cli package was found for the given version then include it in the installation
    if [ -n "${_cli_package_version}" ]; then
        _cli_package+="-${_cli_package_version}"
    fi

    # If a rootless package was found for the given version then include it in the installation
    if [ -n "${_rootless_package_version}" ]; then
        _rootless_package+="-${_rootless_package_version}"
    fi

    _docker_package+="-${_docker_package_version}"

    local _c8d_version
    if version_gte "$MIN_MCR_WITH_C8D_VER"; then
        _c8d_version="$(get_c8d_package_version "${MCR_MAIN_VERSION}" "${_docker_package_version}")"
        if [ -n "${_c8d_version}" ]; then
            _containerd_package+="-${_c8d_version}"
        fi
    fi

    (
        local _zypper_flags="--replacefiles --force --no-confirm --allow-vendor-change"

        if [ -n "${DRY_RUN}" ]; then
            _zypper_flags+=" --dry-run"
        fi

        set -ex
        ${SH_C} "zypper install ${_zypper_flags} ${_docker_package} ${_cli_package} ${_rootless_package} ${_containerd_package}"
    )

}

is_fips_enabled(){
    # if FIPS is enabled on the OS
    local fips
    fips=$(cat /proc/sys/crypto/fips_enabled)
    (( fips > 0 )) && return 0

    # FIPS isn't enabled
    return 1
}

get_channel() {
    declare -r DEFAULT_NONFIPS_EE_CHANNEL="stable-25.0"
    declare -r DEFAULT_FIPS_EE_CHANNEL="${DEFAULT_NONFIPS_EE_CHANNEL}/fips"

    # if CHANNEL is set, use it
    [[ -n "${CHANNEL}" ]] && echo "${CHANNEL}" && return 0
    # if FIPS is enabled in kernel, use the FIPS channel
    is_fips_enabled && echo "${DEFAULT_FIPS_EE_CHANNEL}" || echo "${DEFAULT_NONFIPS_EE_CHANNEL}"
}

ubuntu_install_25() {
    local DEBIAN_FRONTEND
    export DEBIAN_FRONTEND=noninteractive
    (
        set -ex
        $SH_C "apt-get update -qq"
        $SH_C "apt-get install -y -qq curl gnupg" >/dev/null
    )
    local component
    component="$(get_channel)"
    # Download the keyring to the package-updateable location as described
    # in https://wiki.debian.org/DebianRepository/UseThirdParty
    # and configure APT to trust it only for our source's entry.
    local keyring="/usr/share/keyrings/mirantis-archive-keyring.pgp"
    local ubuntu_url
    ubuntu_url="${DOCKER_URL%/}"
    local keyring_asc
    if ! keyring_asc="$(curl -fsSL "$ubuntu_url/gpg")" 2>/dev/null; then
        # URL's may not be suffixed with ubuntu, let's make sure that they are
        if [[ "$ubuntu_url" != */ubuntu$ ]]; then
            ubuntu_url+="/ubuntu"
            keyring_asc="$(curl -fsSL "$ubuntu_url/gpg")"
        fi
    fi
    ( set -ex; $SH_C "gpg --batch --yes --output '${keyring}' --dearmor" ) <<< "$keyring_asc"

    # Add the apt repository for 25.0 in general
    # shellcheck source=/dev/null
    ( set -ex; $SH_C 'tee /etc/apt/sources.list.d/mirantis.sources' ) <<< "\
Types: deb
URIs: $ubuntu_url
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: $component
Architectures: $(dpkg --print-architecture)
Signed-By: $keyring"

    (
        set -ex
        $SH_C "apt-get update -qq"
    )

    local docker_package="${DOCKER_PACKAGE_NAME}"
    local cli_package="${DOCKER_CLI_PACKAGE_NAME}"
    local rootless_package="${DOCKER_ROOTLESS_PACKAGE_NAME}"
    local containerd_package="${CONTAINERD_PACKAGE_NAME}"
    # TODO(dperny): make configurable, in a sane way.
    local cri_package="${CRI_DOCKERD_PACKAGE_NAME}"
    local runc_package="${RUNC_PACKAGE_NAME}"
    (
        set -ex
        # Just do the full install, with all required packages.
        $SH_C "apt-get install -y -qq  ${docker_package} ${cli_package} ${rootless_package} ${containerd_package} ${cri_package} ${runc_package}"
    )
}

yum_install_25() {
    local DIST_ID="$1"
    # unlike ubuntu, with yum and RPMs, we install based on a specific URL.
    local yum_url
    yum_url="${DOCKER_URL%/}"
    local component
    component="$(get_channel)"
    command -v curl &> /dev/null || (
        set -ex
        $SH_C "dnf install -q -y curl"
    )
    # Check if we have a usable repo file before appending suffix
    if ! curl --head -fsSL "$yum_url/gpg" &>/dev/null; then
        if [[ ! "$yum_url" =~ /centos$|/rhel$|rocky$ ]]; then
            yum_url+="/$DIST_ID"
        fi
    fi

    # Add a repository for 25.0 in general
    ( set -ex; $SH_C 'tee /etc/yum.repos.d/docker-ee.repo' ) <<< "[mirantis]
name=Mirantis Container Runtime
baseurl=$yum_url/\$releasever/\$basearch/$component
enabled=1
gpgcheck=1
gpgkey=$yum_url/gpg
module_hotfixes=true"

    # skip all the oracle/rhel 7 stuff; we don't build 25 for those versions.

    local docker_package="${DOCKER_PACKAGE_NAME}"
    local cli_package="${DOCKER_CLI_PACKAGE_NAME}"
    local rootless_package="${DOCKER_ROOTLESS_PACKAGE_NAME}"
    local containerd_package="${CONTAINERD_PACKAGE_NAME}"
    local cri_package="${CRI_DOCKERD_PACKAGE_NAME}"
    local runc_package="${RUNC_PACKAGE_NAME}"

    (
        set -ex
        $SH_C "dnf install -q -y ${docker_package} ${cli_package} ${rootless_package} ${containerd_package} ${cri_package} ${runc_package}"
    )
}

zypper_install_25() {
    # Need some basic info for
    local dist_version
    dist_version=$1
    local arch
    arch="$(uname -m)"
    local component
    component="$(get_channel)"
    local repo_version="${dist_version%%.*}"
    local zypper_flags=" --allow-vendor-change --allow-downgrade --no-confirm"
    # Check if we have curl. If not, we need it.
    command -v curl &> /dev/null || (
        set -ex
        $SH_C "zypper install -y curl"
    )

    local zypper_url
    zypper_url="${DOCKER_URL%/}"
    # no need to append sles if we already have a valid repo
    if ! curl -fsL "$zypper_url/gpg" >/dev/null; then
        zypper_url+="/sles"
    fi
    (
        set -ex
        # you do, in fact, need to removerepo, or running this script a second
        # time on a box will fail (even if you've uninstalled in the meantime,
        # unless your uninstallation involved doing this command)
        $SH_C "zypper removerepo mirantis" # this will always return 0 even if repo alias not found
        $SH_C "zypper addrepo $zypper_url/$repo_version/$arch/$component mirantis"
        $SH_C "rpm --import '$zypper_url/gpg'"
        $SH_C "zypper refresh"
    )

    local docker_package="${DOCKER_PACKAGE_NAME}"
    local cli_package="${DOCKER_CLI_PACKAGE_NAME}"
    local rootless_package="${DOCKER_ROOTLESS_PACKAGE_NAME}"
    local containerd_package="${CONTAINERD_PACKAGE_NAME}"
    local cri_package="${CRI_DOCKERD_PACKAGE_NAME}"
    local runc_package="${RUNC_PACKAGE_NAME}"

    (
        set -ex
        # it looks like suse includes docker by default (installed, but it seems
        # not running), which conflicts with docker-ee (obviously). if someone is
        # running this script, though, they want us to resolve the conflict in our
        # favor, so go ahead and remove anything docker that's already there.
        $SH_C "zypper install $zypper_flags -y  ${docker_package} ${cli_package} ${rootless_package} ${containerd_package} ${cri_package} ${runc_package}"
    )
}

main() {
    local _user
    _user="$(id -un 2>/dev/null || true)"
    if [ "${_user}" != 'root' ]; then
        if command_exists sudo; then
            SH_C='sudo -E sh -c'
        elif command_exists su; then
            SH_C='su -c'
        else
            cat >&2 <<-'EOF'
			Error: this installer needs the ability to run commands as root.
			We are unable to find either "sudo" or "su" available to make this happen.
			EOF
            exit 1
        fi
    fi

    # shellcheck disable=SC1091
    DIST_ID="$(. /etc/os-release && echo "$ID")"
    local _dist_version
    # shellcheck disable=SC1091
    _dist_version="$(. /etc/os-release && echo "$VERSION_ID")"

    if [ -z "${DOCKER_URL}" ]; then
        echo "ERROR: DOCKER_URL must be set, exiting..."
        exit 1
    fi

    if ! mcr_ver "${VERSION}" && [[ "$VERSION" != 25.0* ]]; then
        echo "${VERSION} doesn't match with expected pattern. Version must follow this pattern a.b.c, a.b.c-d, a.b.c-(tp|rc)e, a.b.c-d-(tp|rc)e, where a,b,c,d,e are numbers."
        exit 1
    fi
    MCR_MAIN_VERSION="${BASH_REMATCH[1]}${BASH_REMATCH[2]}"

    local _docker_pkg_ver
    _docker_pkg_ver="$(get_docker_package_version "${VERSION}")"

    case "$DIST_ID:$_dist_version" in
        ubuntu:20.04|ubuntu:22.04)
            # TODO(dperny): All of this checking to see if the version is 25.0 isn't
            # really future-proof, because what about 25.1? But that's tomorrow's
            # problem.
            if [[ $VERSION == 25.0* ]]; then
                ubuntu_install_25
            else
                ubuntu_install "${_docker_pkg_ver}"
            fi
            exit 0
            ;;
        centos:*|rhel:*|rocky:*)
            # Strip point versions, they don't really matter
            if [[ $VERSION == 25.0* ]]; then
                yum_install_25 "$DIST_ID"
            else
                yum_install "${_docker_pkg_ver}" "$DIST_ID" "${_dist_version%%.*}"
            fi
            exit 0
            ;;
        ol:*)
            if [[ $VERSION == 25.0* ]]; then
                yum_install_25 "oraclelinux"
            else
                # Consider only major version for OL distros
                yum_install "${_docker_pkg_ver}" "oraclelinux" "${_dist_version%%.*}"
            fi
            exit 0
            ;;
        sles:15*|opensuse-leap:15*)
            if [[ $VERSION == 25.0* ]]; then
                zypper_install_25 "$_dist_version"
            else
                zypper_install "${_docker_pkg_ver}" "${_dist_version%%.*}"
            fi
            exit 0
            ;;
        *)
            echo
            echo "ERROR: Unsupported distribution / distribution version '${DIST_ID}:${_dist_version}'"
            echo "       If you feel this is a mistake please contact Mirantis support"
            echo
            exit 1
            ;;
    esac
}

main