#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/lib/ulib/ulib" || exit 1
urequire DEFAULTS woinst

function display_help() {
    uecho "$scriptname: Déploiement distant avec woinst

USAGE
    $scriptname [-H host] [-G tmproot] <archive|dir>... [-- options de woinst]

note: à cause d'une limitation de makeself, les options de toinst ne devraient
pas contenir d'espaces ni de caractères spéciaux. L'échappement de ces
caractères n'est pas garanti.

OPTIONS
    -G, --tmproot TMPROOT
        Spécifier le répertoire temporaire sur l'hôte distant, comme par exemple
        /var/tmp. Cette option est utile pour les vservers, qui ont par défaut
        un /tmp minuscule de 16 Mo.
    -S, --ssh SSH
        Spécifier le programme à utiliser pour la connection par ssh.
    -h, --host hosts
    -h, --host @hostsfile
        Spécifier un ou plusieurs hôtes sur lequels faire le déploiement. Pour
        spécifier plusieurs hôtes, il est possible d'utiliser plusieurs fois
        l'option -h, ou spécifier en une seule fois plusieurs hôtes en les
        séparant par un espace ou le caractère ':', e.g. 'host1 host2' ou
        'host1:host2'. Si la spécification contient les caractères { et },
        l'expansion est effectuée, e.g
            -h 'root@{host1,host2}.univ.run'
        Par défaut, la connexion sur l'hôte distant se fait avec l'utilisateur
        root. Il est possible de spécifier un autre utilisateur avec la syntaxe
        user@host, e.g -h user@host
        La forme @hostsfile permet de lire la liste des hôtes depuis le fichier
        hostsfile, à raison d'un hôte par ligne.
    -w, -W, --whost host[:htdocs]
        Comme --host, mais pour le déploiement des resources web. Contrairement
        à --host, un seul hôte peut être spécifié par occurence de l'option,
        pour permettre de spécifier éventuellement le répertoire vers lequel
        copier les resources web. Ainsi, les deux commandes suivantes sont
        équivalentes:
            rwoinst -h host.tld -- -W HTDOCSDIR=HTDOCSBASE/wo
            rwoinst -w host.tld:HTDOCSBASE/wo
        Il est possible de spécifier en une seule invocation --host et --whost
        pour déployer l'application sur un hôte et les resources web sur un
        autre.
    --deploydb
    --nd, --no-deploydb
        Autoriser (respectivement interdire) l'utilisation de la configuration
        locale de déploiement pour identifier la source et/ou la destination
        s'ils ne sont pas spécifiés. Par défaut, la configuration locale de
        déploiement est utilisée.
    -p, --dp, --deploydb-profile PROFILENAME
        Spécifier un ou plusieurs profils séparés par des virgules pour le
        déploiement avec la configuration locale de déploiement. NONE est la
        valeur par défaut et signifie de ne sélectionner que les définitions
        sans profil. ALL signifie de ne pas tenir compte des profils dans les
        définitions.
    -A, --all-profiles
    -P, --prod
    -T, --test
        Raccourcis respectivement pour -pALL, -pprod et -ptest
    -c, --dc, --deploydb-config CONFNAME
        Cette option permet de spécifier un fichier de configuration ou le nom
        de la configuration locale de déploiement à utiliser pour effectuer la
        requête. Par défaut, utiliser le nom 'woinst.conf'"
}

__PARSED_HOSTS=()
__PARSED_FILES=()
function parse_hostsfile() {
    # Lire chacun des fichiers $* et initialiser __PARSED_HOSTS avec la liste
    # des hôtes mentionnés dans les fichiers.
    local inputfile basedir inputs input
    for inputfile in "$@"; do
        inputfile="$(abspath "$inputfile")"
        array_contains __PARSED_FILES "$inputfile" && {
            ewarn "$(ppath "$inputfile"): inclusion récursive"
            continue
        }
        array_add __PARSED_FILES "$inputfile"
        basedir="$(dirname "$inputfile")"

        array_from_lines inputs "$(<"$inputfile" filter_conf)" || {
            ewarn "$inputfile: fichier ingnoré"
            continue
        }
        for input in "${inputs[@]}"; do
            if [ "${input#@}" != "$input" ]; then
                # fichier inclus
                parse_hostsfile "$(abspath "${input#@}" "$basedir")"
            else
                array_addu __PARSED_HOSTS "$input"
            fi
        done
    done
}
function __expand_braces() {
    if [[ "$1" == *{* ]] && [[ "$1" == *}* ]]; then
        eval "echo $1"
    else
        echo "$1"
    fi
}
function __dot_is_localhost() { [ "$1" == "." ] && echo "localhost" || echo "$1"; }
function fix_hosts() {
    # Si hosts contient des éléments multiple, comme a:b, séparer ces
    # éléments. i.e (a b:c) --> (a b c)
    # Supporter la syntaxe @hostsfile qui permet de charger la liste des hôtes
    # depuis un fichier.
    # Remplacer aussi les '.' par 'localhost'
    array_map hosts __expand_braces
    array_fix_paths hosts ":"
    array_fix_paths hosts " "

    local -a _hosts _tmphosts host
    for host in "${hosts[@]}"; do
        host="${host%/}"
        if [ "${host#@}" != "$host" ]; then
            __PARSED_HOSTS=()
            parse_hostsfile "${host#@}"
            array_fix_paths __PARSED_HOSTS
            array_extendu _hosts __PARSED_HOSTS
        else
            array_addu _hosts "$host"
        fi
    done
    array_copy hosts _hosts
    array_map hosts __dot_is_localhost
}

function deploy_to() {
    local archive="$1" host="$2" tmproot="$3"
    shift; shift; shift

    local r=
    if [ "$host" == "localhost" ]; then
        etitle "Sur l'hôte local"
        "$archive" ${tmproot:+--tmproot "$tmproot"} -- "$@" || r=1
        eend
    else
        local user
        local archivename="$(basename "$archive")"

        splituserhost "$host" user host
        [ -n "$user" ] || user=root

        # sur l'hôte distant, ne rendre non interactif qu'à partir de -yy
        rinteraction=$__interaction
        [ $rinteraction -lt 2 ] && rinteraction=$(($rinteraction + 1))

        etitle "Vers $user@$host"

        estep "Copie de l'archive"
        scp -S "$SSH" "$archive" "$user@$host:" || r=1

        if [ -z "$r" ]; then
            estep "Lancement du script de déploiement"
            "$SSH" -qt "$user@$host" "\
__interaction=$rinteraction
__estack=$(qval "$__estack")
__tlevel=$(qval "$__tlevel")
export __interaction __estack __tlevel
${UTOOLS_LANG:+UTOOLS_LANG='$UTOOLS_LANG'; export UTOOLS_LANG
}$(qvals "./$archivename" ${tmproot:+--tmproot "$tmproot"} -- MYHOST="$host" "$@")" || r=1
        fi

        eend
    fi
    return ${r:-0}
}

action=deploy
tmproot=
SSH=
hosts=()
whosts=()
ddb_enable=1
ddb_profile=NONE
ddb_conf=woinst.conf
parse_opts "${PRETTYOPTS[@]}" \
    --help '$exit_with display_help' \
    -G:,--tmproot: tmproot= \
    -S:,--ssh: SSH= \
    -h:,-H:,--host: hosts \
    -w:,-W:,--whost: whosts \
    --deploydb ddb_enable=1 \
    --nd,--no-deploydb ddb_enable= \
    -p:,--dp:,--deploydb-profile ddb_profile= \
    -A,--all-profiles ddb_profile=ALL \
    -P,--prod ddb_profile=prod \
    -T,--test ddb_profile=test \
    -c:,--dc:,--deploydb-config ddb_conf= \
    @ args -- "$@" && set -- "${args[@]}" || die "$args"

: "${SSH:=ssh}"

## Bundle à déployer et hôtes sur lesquels faire le déploiement

# quels informations avons-nous?
array_isempty hosts && has_hosts= || has_hosts=1

if [ $# -eq 0 ] || [[ "$1" == -* ]] || [[ "$1" == *=* ]]; then
    # pas d'argument, ou c'est une option (qui fait donc partie des arguments de woinst)
    has_bundle=
else
    bundle="$1"
    has_bundle=1
    shift
fi

# configuration locale de déploiement
ddb_enable= #XXX 05/04/2021 désactiver deploydb pour le moment
if [ -n "$ddb_enable" ]; then
    # filtrer les options... elles seront ignorées si on passe finalement par
    # deploydb
    vars=()
    options=()
    for arg in "$@"; do
        if [[ "$arg" == *=* ]]; then
            array_add vars "$arg"
        else
            array_add options "$arg"
        fi
    done

    deploydb=(
        "$scriptdir/lib/nulib/deploydb"
        --missing-ok
        ${ddb_conf:+-c "$ddb_conf"}
        -m woinst
        --run -r woinst.query_rwoinst "$script"
    )
    cmds=()
    myname="$(basename "$(pwd)")"
    if [ -n "$has_hosts" ]; then
        fix_hosts
        for host in "${hosts[@]}"; do
            array_from_lines tmpcmds "$("${deploydb[@]}" "$bundle" "$host" "$ddb_profile" "${vars[@]}")"
            array_extend cmds tmpcmds
        done
    elif [ -n "$has_bundle" ]; then
        array_from_lines tmpcmds "$("${deploydb[@]}" "$bundle" "" "$ddb_profile" "${vars[@]}")"
        array_extend cmds tmpcmds
    elif [[ "$myname" == *.woa ]] || [[ "$myname" == *.framework ]]; then
        read_value "Veuillez entrer le chemin du bundle à déployer" bundle . O
        has_bundle=1
        array_from_lines tmpcmds "$("${deploydb[@]}" "$bundle" "" "$ddb_profile" "${vars[@]}")"
        array_extend cmds tmpcmds
    fi
    if [ ${#cmds[*]} -gt 0 ]; then
        [ ${#options[*]} -gt 0 ] && ewarn "Les options supplémentaires '${options[*]}' seront ignorées"
        if check_interaction -c; then
            if [ ${#cmds[*]} -eq 1 ]; then
                einfo "La commande suivante va être lancée:"
                eecho "\$ $script --no-deploydb \\"
                for cmd in "${cmds[0]}"; do
                    eecho "      ${cmd#$script --no-deploydb }"
                done
            else
                einfo "Les commandes suivantes seront lancées:"
                eecho "\$ $script --no-deploydb \\"
                for cmd in "${cmds[@]}"; do
                    eecho "  ... ${cmd#$script --no-deploydb }"
                done
            fi
            read -p "Confirmez ou attendez 4 secondes [On] " -t 4 r
            if [ $? -gt 128 ]; then
                echo # cosmetic
            elif [ $? -le 128 -a -n "$r" ]; then
                is_yes "$r" || die
            fi
        fi
        r=0
        for cmd in "${cmds[@]}"; do
            einfo "$cmd"
            eval "$cmd" || r=1
        done
        exit $r
    elif [ "$ddb_profile" != "ALL" ]; then
        ewarn "Aucune configuration locale de déploiement n'a été trouvée pour le profil $ddb_profile"
    fi
fi

if [ -z "$has_bundle" ]; then
    # pas d'argument, ou c'est une option (qui fait donc partie des arguments de woinst)
    read_value "Veuillez entrer le chemin du bundle à déployer" bundle "$bundle" O
    has_bundle=1
fi
[ -n "$has_bundle" ] && set -- "$bundle" "$@"

array_isempty hosts && read_value "Entrez une liste d'hôtes séparés par ':'" hosts "localhost"
fix_hosts

## Création de l'archive

etitle "Création du répertoire de travail"
ac_set_tmpdir workdir

bundles=()
while [ -n "$1" ]; do
    if [ "$1" == "--" ]; then
        # début des options de woinst
        shift
        break
    elif [[ "$1" == -* ]] || [[ "$1" == *=* ]]; then
        # début des options de woinst
        break
    fi

    src="$1"; shift
    if [ -f "$src" ] && is_archive "$src"; then
        estep "$(ppath "$src")"
        cp_R "$src" "$workdir" || die
    elif [ -d "$src" ]; then
        src="$(abspath "$src")"
        setx srcname=basename -- "$src"
        case "$src" in
        *.woa|*.framework) ;;
        *) # support projet maven
            if [ ! -f "$src/pom.xml" ]; then
                : # non, pas un projet maven
            elif [ -d "$src/target/$srcname.woa" ]; then
                src="$src/target/$srcname.woa"
            elif [ -d "$src/target/$srcname.framework" ]; then
                src="$src/target/$srcname.framework"
            fi
            ;;
        esac
        if endswith "$src" .framework; then
            is_wofwkdir "$src" || die "Framework invalide: $(ppath "$src")"
        elif endswith "$src" .woa; then
            is_woappdir "$src" || die "Application invalide: $(ppath "$src")"
        else
            die "N'est pas un bundle valide: $(ppath "$src")"
        fi
        estep "$(ppath2 "$src")"
        cp_R "$src" "$workdir" || die
    else
        die "Fichier ou répertoire introuvable: $src"
    fi

    src="$(abspath "$src")"
    array_add bundles "$(basename "$src")"
done

estep "Copie de l'environnement de déploiement"
mkdir "$workdir/lib"
ulibsync "$workdir/lib"
cp "$scriptdir/woinst.sh" "$workdir"
chmod +x "$workdir/woinst.sh"

eend

etitle "Création de l'archive pour le déploiement"
ac_set_tmpfile archive

"$scriptdir/mkusfx" --bare --tmp-archive -o "$archive" "$workdir" -- ./woinst.sh --is-tmpdir "${bundles[@]}" || die

eend

## Déploiement

etitle "Déploiement des bundles"
for host in "${hosts[@]}"; do
    deploy_to "$archive" "$host" "$tmproot" "$@" "${woinst_options[@]}" || die
done
eend

if [ ${#whosts[*]} -gt 0 ]; then
    etitle "Déploiement des resources web"
    for hostdestdir in "${whosts[@]}"; do
        splitpair "$hostdestdir" host destdir
        deploy_to "$archive" "$host" "$tmproot" \
                  -y -W ${destdir:+HTDOCSDIR="$destdir"} "$@" "${woinst_options[@]}" || die
    done
    eend
fi