#!/bin/sh

# This script automates binary upgrade testing.

# Sample run:
# OS_NAME=ubuntu OS_VERSION=24.04 tools/testbin -version 1.11.0 -bindir .. -pgversions '13 14' deb

# A released toolkit lists the versions it is upgradeable from in
# extension/timescaledb_toolkit.control .  This script processes those entries
# and for each version from which upgrading is supported:
# 1. Install old binaries (deb or rpm) for each supported postgresql release
# 2. Run the 1st half of the upgrade tests
# 3. Install the binary for the version under test
# 4. Run the 2nd half of the upgrade tests

# The distinction between environment variables and command-line options is
# possibly inconsistent.  The approach now is for general parameters to come
# from the command line, and system-specific parameters to come from the
# environment.  Specifically, these are required in the environment for deb
# packages only:
# - OS_NAME
# - OS_VERSION

set -ex

# Minimum version we support arm64 deb - could possibly go lower.
# I know 1.8 at least builds on arm.
MIN_DEB_ARM=1.10.1
# We added 1: epoch at 1.7.0.
MIN_DEB_EPOCH=1.7.0
# TODO Unfortunate that pgrx allows neither specifying nor querying the port it
#   starts postgres on, so we duplicate that knowledge.  Watch out for
#   that changing!
PGRX_PORT_BASE=28800
CONTROL=extension/timescaledb_toolkit.control

# For PG_VERSIONS default.
. tools/dependencies.sh

print() {
    printf '%s\n' "$*"
}

die() {
    st=${?:-0}
    if [ $st -eq 0 ]; then
        st=2
    fi
    print "$*" >&2
    exit $st
}

usage() {
    die 'testbin [-n] -bindir DIR -version VERSION -pgversions "[V1] [V2]..." ( ci | deb | rpm )'
}

# Requires:
# - PGRX_PORT_BASE
# - PG_VERSION
# Sets:
# - PG_PORT
select_pg() {
    PG_PORT=$(( $PGRX_PORT_BASE + $PG_VERSION ))
}

# Start postgres and run the first half (old toolkit) of the test.
# Must select_pg first.
start_test() {
    $nop cargo pgrx start --package timescaledb_toolkit pg$PG_VERSION
    $nop cargo run --manifest-path tools/update-tester/Cargo.toml -- create-test-objects -u $LOGNAME -h 127.1 -p $PG_PORT
}

# Run the second half (new toolkit) of the test and stop postgres.
# Must select_pg first.
finish_test() {
    $nop cargo run --manifest-path tools/update-tester/Cargo.toml -- validate-test-objects -u $LOGNAME -h 127.1 -p $PG_PORT
    $nop cargo pgrx stop --package timescaledb_toolkit pg$PG_VERSION
}

deb_init() {
    [ -n "$OS_NAME" ] || die 'OS_NAME environment variable must be set to the distribution name e.g. debian or ubuntu'
    [ -n "$OS_VERSION" ] || die 'OS_VERSION environment variable must be set to the distribution version number'

    ARCH=`dpkg --print-architecture`
    EPOCH=
    MIN_DEB_ARM=`cmp_version $MIN_DEB_ARM`
    MIN_DEB_EPOCH=`cmp_version $MIN_DEB_EPOCH`
}

# Requires:
# - FROM_VERSION
skip_from_version() {
    # We released 1.10.0-dev by accident.  We have to support upgrades
    # from it (and we tested that at the time), but we pulled the binaries, so
    # we can't test it here.
    [ $FROM_VERSION = 1.10.0-dev ] && return
    [ $OS_NAME = debian ] && [ $OS_VERSION = 10 ] && [ `cmp_version $FROM_VERSION` -lt 011100 ] && return
    [ $OS_NAME = ubuntu ] && [ $OS_VERSION = 22.04 ] && [ `cmp_version $FROM_VERSION` -lt 010600 ] && return
}

# Requires:
# - FROM_VERSION
# - PG_VERSION
skip_from_version_pg_version() {
    # skip versions without binaries for this PostgreSQL version
    [ $PG_VERSION -gt 14 ] && [ `cmp_version $FROM_VERSION` -lt 011301 ] && return
    [ $PG_VERSION -gt 15 ] && [ `cmp_version $FROM_VERSION` -lt 011801 ] && return
}

# Requires:
# - FROM_VERSION
deb_start_test() {
    skip_from_version && return 1
    cmp_version=`cmp_version $FROM_VERSION`
    [ "$ARCH" = arm64 ] && [ $cmp_version -lt $MIN_DEB_ARM ] && return 1

    [ $cmp_version -ge $MIN_DEB_EPOCH ] && EPOCH=1:
    for PG_VERSION in $PG_VERSIONS; do
        skip_from_version_pg_version && continue
        select_pg $PG_VERSION
        deb=timescaledb-toolkit-postgresql-${PG_VERSION}=${EPOCH}${FROM_VERSION}~${OS_NAME}${OS_VERSION}
        $nop sudo apt-get -qq install $deb || die

        start_test || die
    done
}

test_deb() {
    deb_init
    for FROM_VERSION; do
        deb_start_test || continue
        for PG_VERSION in $PG_VERSIONS; do
            skip_from_version_pg_version && continue
            select_pg $PG_VERSION
            deb=timescaledb-toolkit-postgresql-${PG_VERSION}_${TOOLKIT_VERSION}~${OS_NAME}${OS_VERSION}_${ARCH}.deb
            $nop sudo dpkg -i "$BINDIR/$deb"

            finish_test

            $nop sudo dpkg -P timescaledb-toolkit-postgresql-$PG_VERSION
        done
    done
}

test_ci() {
    deb_init

    # When run under CI after a recent release, the Packages file in the
    # container image don't know about the latest version.
    $nop sudo apt-get update

    for FROM_VERSION; do
        deb_start_test || continue
        for PG_VERSION in $PG_VERSIONS; do
            skip_from_version_pg_version && continue
            select_pg $PG_VERSION
            $nop sudo dpkg -P timescaledb-toolkit-postgresql-$PG_VERSION
            # Installing (and possibly uninstalling) toolkit binary gives this back to root but we need to write to it.
            $nop sudo chown $LOGNAME /usr/lib/postgresql/$PG_VERSION/lib /usr/share/postgresql/$PG_VERSION/extension
            $nop tools/build -pg$PG_VERSION install

            finish_test
        done
    done
}

rpm_start_test() {
    for PG_VERSION in $PG_VERSIONS; do
        skip_from_version_pg_version && continue
        select_pg $PG_VERSION
        rpm=timescaledb-toolkit-postgresql-$PG_VERSION
        # yum doesn't seem to allow force-install of a specific version.
        # If the package is already installed at a different version,
        # the install command below does nothing.
        # So, uninstall if installed.
        $nop rpm -q $rpm > /dev/null && $nop sudo rpm -e $rpm
        $nop sudo yum -q -y install $rpm-$FROM_VERSION

        start_test
    done
}

test_rpm() {
    ARCH=`rpm -E '%{_arch}'`
    for FROM_VERSION; do
        skip_from_version && continue
        rpm_start_test
        for PG_VERSION in $PG_VERSIONS; do
            skip_from_version_pg_version && continue
            select_pg $PG_VERSION
            rpm=timescaledb-toolkit-postgresql-$PG_VERSION-$TOOLKIT_VERSION-0.el$OS_VERSION.$ARCH.rpm
            $nop sudo rpm -U "$BINDIR/$rpm"

            finish_test

            $nop sudo rpm -e timescaledb-toolkit-postgresql-$PG_VERSION
        done
    done
}

test_rpm_ci() {
    for FROM_VERSION; do
        skip_from_version && continue
        rpm_start_test
        for PG_VERSION in $PG_VERSIONS; do
            skip_from_version_pg_version && continue
            select_pg $PG_VERSION

            $nop sudo rpm -e timescaledb-toolkit-postgresql-$PG_VERSION
            $nop sudo chown -R $LOGNAME /usr/pgsql-$PG_VERSION/lib /usr/pgsql-$PG_VERSION/share/extension
            $nop tools/build -pg$PG_VERSION install

            finish_test
        done
    done
}

# Format 3-part version string for numeric comparison.
# If this script has survived to see one of the 3 parts incremented past 99:
# congratulations!  It is not hard to fix.
cmp_version() {
    minpat=${1#*.}
    printf '%02d%02d%02d' ${1%%.*} ${minpat%.*} ${minpat#*.} 2> /dev/null
}

print_upgradeable_from() {
    # TODO We never shipped a 1.4 deb and the 1.5 deb is called 1.5.0
    #  Let's draw the line there and remove those from upgradeable_from.
    #  Someone who needs to upgrade from 1.4 or 1.5 can upgrade to 1.10.1 and then beyond.
    sed -n "s/'//g; s/,//g; s/^# upgradeable_from = 1\.4 1\.5 //p" $CONTROL
}

cleanup() {
    set +e
    for PG_VERSION in $PG_VERSIONS; do
        select_pg $PG_VERSION
        $nop cargo pgrx stop --package timescaledb_toolkit pg$PG_VERSION
    done
}

run() {
    [ -n "$LOGNAME" ] || die 'LOGNAME environment variable must be set to the login name'
    [ -n "$PG_VERSIONS" ] || die '-pgversions required'
    # TODO Requiring -bindir and -version when not all methods need them is awkward but eh.
    [ -d "$BINDIR" ] || die '-bindir required'
    [ -n "$TOOLKIT_VERSION" ] || die '-version required'

    trap cleanup 0
    test_$1 `print_upgradeable_from`
    trap - 0

    echo DONE
}

while [ $# -gt 0 ]; do
    arg="$1"
    shift
    case "$arg" in
        -n)
            nop=:
            ;;

        -bindir)
            BINDIR=$1
            shift
            ;;

        -pgversions)
            PG_VERSIONS=$1
            shift
            ;;

        -version)
            TOOLKIT_VERSION=$1
            shift
            ;;

        ci|deb|rpm|rpm_ci)
            run $arg
            ;;

        *)
            usage
            ;;
    esac
done
