#!/usr/bin/env bash
#
# run given command via Docker on all distros supported by ReaR, or only a single one

# command line args:
#
# $0 [os ...] -- <command> [args ...]
#
# Note: This script works also on MacOS and makes an effort to use cross-platform
# compatible options for tools like cp

# Define the list of supported images
declare -r IMAGES=(
    ubuntu:{20.04,22.04,24.04}
    debian:{10,11,unstable}
    opensuse/leap:15
    registry.suse.com/suse/sle15
    centos:8       # discontinued
    quay.io/centos/centos:stream9
    # registry.access.redhat.com/ubi{7,8,9} basic packages like parted are missing
    fedora:{40,41} # rawhide Docker image is broken, see https://github.com/fedora-cloud/docker-brew-fedora/issues/109
    archlinux
    manjarolinux/base
)

declare -r HELP_TEXT="
$0 [image ...] -- <command> [args ...]
specify image patterns or omit image to run in all supported images:

$(fold -sw 60 <<<"${IMAGES[*]}")

You can also specify completely different Docker images instead.

Without command it will show the Bash version in all images

Special commands:

--patch     Patch the (given or all) images to contain the ReaR build and run dependencies
            Add --continue-and-record-successful FILE to continue after a failure and record
            the successful images in FILE
-i          Interactive shell, used by default if image selection yields only a single image

Architecture defaults to the host platform, specify architecture via
-a <architecture>, e.g. -a amd64 on M1 Mac as part of the image selection
"


function gh_actions_output {
    if test "$GITHUB_ACTIONS"; then
        echo "$*"
    fi
}

function die {
    gh_actions_output "::error::$*"
    echo -e "ERROR: $*" 1>&2
    exit 1
}

function exit_handler {
    echo "** SCRIPT RUN TIME $SECONDS SECONDS **"
}
trap exit_handler EXIT

extra_docker_args=()
use_images=()

# patch the images to contain all ReaR build and run dependencies
function patch_images() {
    local continue_and_record_successful_file=""
    while test $# -gt 0; do
        case "$1" in
        --continue-and-record-successful)
            test $# -lt 2 && die "Missing filename for $1 argument"
            continue_and_record_successful_file="$2"
            rm -vf "$continue_and_record_successful_file"
            shift 2
            ;;
        *)
            die "Unknown option $1"
            ;;
        esac
    done

    for image in "${use_images[@]}"; do
        local DOCKERFILE="
FROM $image
LABEL description=\"$image patched to build and run ReaR\"
SHELL [\"/bin/bash\", \"-xeuo\", \"pipefail\", \"-c\"]
ENV DEBIAN_FRONTEND=noninteractive

RUN type -p apt-get &>/dev/null || exit 0 ;\
    apt-get -y update ;\
    apt-get -y --allow-unauthenticated install </dev/null \
        sysvinit-utils kbd cpio file procps ethtool iputils-ping net-tools dosfstools binutils parted openssl gawk attr bc psmisc nfs-client portmap xorriso isolinux gdisk syslinux syslinux-common syslinux-efi iproute2 \
        make asciidoctor git build-essential debhelper devscripts ;\
    apt-get -y --allow-unauthenticated install </dev/null fdisk ||\
        apt-get -y --allow-unauthenticated install </dev/null util-linux

RUN type -p zypper &>/dev/null || exit 0 ;\
    zypper --no-gpg-checks --quiet --non-interactive install \
        sysvinit-tools kbd cpio binutils ethtool gzip iputils parted tar openssl gawk attr bc syslinux portmap rpcbind iproute2 nfs-client xorriso mkisofs util-linux psmisc procps \
        make git rpm-build ;\
    zypper --no-gpg-checks --quiet --non-interactive install 'rubygem(asciidoctor)' || \
        zypper --no-gpg-checks --quiet --non-interactive install asciidoc xmlto

RUN type -p pacman &>/dev/null || exit 0 ;\
    pacman --noconfirm -Sy \
        sysvinit-tools kbd cpio binutils ethtool gzip iputils parted tar openssl gawk attr bc syslinux rpcbind iproute2 nfs-utils libisoburn cdrtools util-linux psmisc procps-ng util-linux diffutils less \
        make binutils fakeroot git asciidoctor debugedit

# CentOS 8 doesn't have sysvinit-tools any more but it also doesn't have asciidoctor yet
RUN type -p yum &>/dev/null || exit 0 ;\
    grep -E '(CentOS.*Final|CentOS Linux release 8)' /etc/redhat-release && \
        sed -i -e 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|' -e '/^mirror/d' /etc/yum.repos.d/*.repo ; \
    yum -q --nogpgcheck install -y \
        kbd cpio binutils ethtool gzip iputils parted tar openssl gawk attr bc syslinux rpcbind iproute nfs-utils xorriso genisoimage util-linux psmisc procps-ng util-linux \
        make binutils git rpm-build ; \
    yum -q --nogpgcheck install -y sysvinit-tools mkisofs asciidoc xmlto || \
        yum -q --nogpgcheck install -y asciidoctor || \
            yum -q --nogpgcheck install -y mkisofs asciidoc xmlto

RUN git config --global --add safe.directory /rear
"

        # TODO: Add support for GRUB2
        gh_actions_output "::group::Patching $image"
        printf "********** PATCHING %-40s **********\n" "$image" 1>&2
        read oldsize junk < <(docker images --format '{{.VirtualSize}}' "$image")
        docker buildx build -t "$image" \
            "${extra_docker_args[@]}" \
            - <<<"$DOCKERFILE"
        local docker_build_ret=$?
        if test "$continue_and_record_successful_file" ; then
            if test $docker_build_ret -eq 0 ; then
                echo "$image" >> "$continue_and_record_successful_file"
            else
                printf "********** FAILED   %-40s **********\n" "$image" 1>&2
                continue
            fi
        else
            test $docker_build_ret -eq 0 || die "Failed building $image"
        fi
        read newsize junk < <(docker images --format '{{.VirtualSize}}' "$image")
        test "$oldsize" != "$newsize" && printf "********** %-35s %7s -> %-7s *****\n" "$image" "$oldsize" "$newsize"
        gh_actions_output "::endgroup::"
    done
}

command_args=()
while test $# -gt 0; do
    case "$1" in
    -a)
        echo "Using architecture $2 instead of default Docker architecture $(docker system info --format '{{.Architecture}}')"
        extra_docker_args+=("--platform" "linux/$2")
        shift 2
        ;;
    -h | --help)
        echo "$HELP_TEXT"
        exit 1
        ;;
    --)
        shift
        [[ $# -gt 0 ]] && command_args=("$@")
        break
        ;;
    *)
        #shellcheck disable=SC2207
        use_images+=($(
            ((c = 0))
            for image in "${IMAGES[@]}"; do
                if [[ "$image" == *$1* ]]; then
                    echo "$image"
                    ((c++))
                fi
            done
            if ((c == 0)); then
                echo "$1"
            fi
        ))
        shift
        ;;
    esac
done

if test ${#use_images[@]} -eq 0; then
    use_images=("${IMAGES[@]}")
fi

if test ${#command_args[@]} -eq 0; then
    if test ${#use_images[@]} -eq 1; then # if only one image is given and no command then go interactive
        command_args=(-i)
    else
        command_args=("echo" "Bash is \$BASH_VERSION")
    fi
fi

rear_toplevel_dir=$(dirname $(dirname $(readlink -f "$0")))
bash_script=tools/run-in-docker-script.sh
bash_args=(-c "echo BUG: CONFIGURATION ERROR" )

# declare -p IMAGES use_images command_args rear_toplevel_dir ; exit 0

case "${command_args[0]}" in
--patch)
    patch_images "${command_args[@]:1}"
    exit $?
    ;;
-i)
    bash_args=( -i )
    ;;
-*)
    bash_args=( "${command_args[@]}" )
    ;;
*)
    echo "${command_args[*]}" >"$rear_toplevel_dir/$bash_script"
    bash_args=("$bash_script")
    ;;
esac


# Note: bash reads --rcfile <file> for interactive shells
#       bash reads the file specified in BASH_ENV for non-interactive shells
#       we set both so that our startup file run-in-docker.bashrc is ALWAYS read as it sets the PATH
for image in "${use_images[@]}"; do
    gh_actions_output "::group::Running $image"
    printf "********** %-40s **********\n" "$image" 1>&2
    image_name="$(echo -n "$image" | tr -cs '0-9a-zA-Z-_' -)"
    dist_dest="dist-all/$image_name"
    mkdir -p "$rear_toplevel_dir/$dist_dest" || die "Could not mkdir $rear_toplevel_dir/$dist_dest"
    docker run \
        --rm $(test -t 0 && echo -i -t) \
        --sig-proxy=false \
        -h "$image_name" \
        -v "$rear_toplevel_dir:/rear" \
        -e REAR_VAR=/tmp/rear \
        -e BASH_ENV=tools/run-in-docker.bashrc \
        -w /rear \
        "${extra_docker_args[@]}" \
        "$image" \
        /bin/bash --rcfile tools/run-in-docker.bashrc "${bash_args[@]}" || die "############### DOCKER RUN FAILED FOR $image"
    if test "$(ls -l "$rear_toplevel_dir/dist" 2>/dev/null || :)"; then
        echo "********** Copying dist to ${dist_dest}"
        cp -R -x "$rear_toplevel_dir/dist/." "$rear_toplevel_dir/$dist_dest/" || die "Could not copy dist to $dist_dest"
    fi
    gh_actions_output "::endgroup::"
done
