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

function display_help() {
    uecho "$scriptname: outil pour faciliter l'utilisation de docker

USAGE
    $scriptname CMDs...

Si une commande commence ou se termine par un underscore, e.g '_ls', elle est
passée telle quelle à docker (en enlevant l'underscore d'abord, bien sûr). Si
une commande ne figure pas dans la liste ci-dessous, elle est passée telle
quelle à docker.

Certaines commandes ci-dessous ont le même nom que des commandes docker, et
nécessitent un projet docker ou docker-compose. Si ces commandes sont lancées
alors qu'on n'est pas dans un projet docker, alors elles sont passées telle
quelles à docker. ATTENTION: comme la commande est exécutée par docker, cela
veut dire qu'elle n'aura pas la même sématique.

COMMANDES
    get-profile
        Afficher le profil courant
    b|build [NAME=VALUE...] [SERVICE]
        Construire les images. Les variables NAME=VALUE sont des 'build args'
    p|push
    p|push [SERVICES...]
        La première syntaxe est utilisable avec un projet docker. Elle permet de
        pousser l'image qui a été construite avec build vers le serveur
        La deuxième syntaxe est utilisée avec un projet docker-compose. Elle
        permet de pousser les images correspondant aux services qui ont été
        construit vers le serveur.
    s|start [SERVICE]
        Démarrer le(s) service(s)
    k|stop [SERVICE]
        Arrêter le(s) service(s)
    1|up
        Créer l'environnement, démarrer les services et suivre les logs de façon
        interactive.
        Vaguement équivalent à -- start -- logs
    l|logs [SERVICE]
        Afficher les logs
    0|down
        Arrêter les services et supprimer l'environnement
    r|run SERVICE [COMMAND]
        Démarrer le service en mode interactif avec la commande spécifiée
        Les options suivantes sont supportées, mais il faut les spécifier avant
        SERVICE:
        * pour docker et docker-compose: --volume
        * uniquement pour docker-compose: --detach, -e, --label, --no-deps,
          --service-ports, --use-aliases
        Par défaut, --rm est activé; utiliser --no-rm pour ne pas supprimer le
        container après utilisation.
    x|exec SERVICE COMMAND
        Lancer une commande dans le container correspondant au service spécifié,
        qui doit être en fonctionnement. Si la commande n'est pas spécifiée, la
        valeur par défaut est bash. Avec les options --select-service et
        --select-machine, l'argument SERVICE vaut par défaut 'SS'
        Un pseudo-tty est alloué pour la commande et STDIN est ouvert.
    d|brd [NAME=VALUE...]
        Construire les images (comme avec build), démarrer les services et
        suivre les logs de façon interactive (comme avec up). Dès que l'on
        arrête l'affichage des logs avec Ctrl+C, arrêter les services et
        supprimer l'environnement (comme avec down)
        Vaguement équivalent à -- build -- start [args] -- logs
                      suivi de -- down
    bs [NAME=VALUE...]
        Construire les images (comme avec build) puis démarrer les services
        (comme avec start)
        Equivalent à -- build -- start [args]
    br [NAME=VALUE...] SERVICE [COMMAND]
        Construire les images (comme avec build) puis démarrer le service avec
        la commande spécifiée (comme avec run)
        Equivalent à -- build [NAME=VALUE...] -- run [args]
    y|deploy [args...]
        (Re)déployer un stack. Cette commande ne fonctionne qu'en mode swarm.
        Implique --stack
    by|bd [NAME=VALUE...] [args...]
        Equivalent à --stack -- build [NAME=VALUE...] -- deploy args...
        Utilisable notamment en développement
    bp [NAME=VALUE...] [args...]
        Equivalent à --stack -- build [NAME=VALUE...] -- push args...
    bpy|bpd [NAME=VALUE...] [args...]
        Equivalent à --stack -- build [NAME=VALUE...] -- push -- deploy args...
    ser|service COMMAND SERVICE [args...]
        Frontend pour 'docker service COMMAND args... SERVICE'
        Cette commande ne fonctionne qu'en mode swarm. Il n'est pas nécessaire
        de préfixer le nom du service avec le nom du stack, pour être cohérent
        avec les autres commandes
        IMPORTANT: notez que les arguments sont placés avant le nom du service.
        Celà signifie qu'on ne peut spécifier que des options à la commande.
        Penser aussi à protéger ces options de l'analyse eg.
            $scriptname -- service logs web -f
        Pour des cas d'utilisation plus complexe, il faut lancer directement
        docker service
    sta|status [SERVICE] [num]
        Afficher le status du service spécifié: pour chaque occurence, sur quel
        noeud il tourne (ou tournait), quel est le statut actuel ainsi que le
        message associé. Si le service n'est pas spécifié, prendre la liste des
        services affichée par docker service ls
        'num' est le numéro de l'instance de service. Si ce numéro est précisé,
        afficher les informations uniquement pour cette instance là, ainsi que
        les logs
        Il est possible de spécifier un filtre docker name=value avant 'num'. Ce
        filtre est utilisé pour sélectionner certaines instances du service.
            - id=<ID> a task's ID ou prefix
            - name=<string> a task's name or prefix
            - node=<string> a node's name or ID
            - desired-state=(running|shutdown|accepted)
        Pour faciliter l'utilisation des filtres, certains aliases sont définis.
        '=r', '=k' et '=a' sont des aliases pour desired-state=running,
        desired-state=shutdown et desired-state=accepted respectivement
    u|update SERVICE [args...]
        Mettre à jour un service, équivalent à 'service update SERVICE'
    scale SERVICE=REPLICAS [args...]
        Mettre à jour le nom de réplicas d'un service, équivalent à la commande
        'service scale SERVICE=REPLICAS'
    ip|show-ip [SERVICE]
        Afficher l'adresse IP interne du service
    systemd|systemd-unit
        Générer une unité systemd qui démarre les services. A priori, ce n'est
        nécessaire que si aucune politique de redémarrage n'a été définie.
    cp|copy CONTAINER:SRC DEST
    cp|copy SRC CONTAINER:DEST
        Copier un fichier ou un répertoire depuis/vers un container. Avec les
        options --select-service et --select-machine, il est possible d'utiliser
        'SS' pour signifier le container sélectionné e.g
            $scriptname -s X_Y -- cp file SS:path/to/dir
    ps [filter|name=value]
        Afficher les containers en cours d'exécution
        Le filtre est une expression régulière de type awk qui est mise en
        correspondance avec les noms de l'image et de la tâche.
        Il est aussi possible d'utiliser un filtre docker de la forme name=value
            - ancestor=(<image-name>[:tag]|<image-id>| <image@digest>)
              containers created from an image or a descendant.
            - before=(<container-name>|<container-id>)
            - expose=(<port>[/<proto>]|<startport-endport>/[<proto>])
            - exited=<int> an exit code of <int>
            - health=(starting|healthy|unhealthy|none)
            - id=<ID> a container's ID
            - is-task=(true|false)
            - label=<key> or label=<key>=<value>
            - name=<string> a container's name
            - network=(<network-id>|<network-name>)
            - publish=(<port>[/<proto>]|<startport-endport>/[<proto>])
            - since=(<container-name>|<container-id>)
            - status=(created|restarting|removing|running|paused|exited)
            - volume=(<volume name>|<mount point destination>)
    ls [filter|name=value]
        Lister les images actuellement présentes
        Le filtre est une expression régulière de type awk qui est mise en
        correspondance avec le nom de l'image. Un suffixe :tag permet de ne
        sélectionner que les images correspondant au filtre qui ont le tag
        spécifié.
        Il est aussi possible d'utiliser un filtre docker de la forme name=value
            - dangling=(true|false) - find unused images
            - label=<key> or label=<key>=<value>
            - before=(<image-name>[:tag]|<image-id>| <image@digest>)
            - since=(<image-name>[:tag]|<image-id>| <image@digest>)
            - reference=(pattern of an image reference)
    pull filter
        Mettre à jour une ou plusieurs images
        Le filtre est une expression régulière de type awk qui est mise en
        correspondance avec le nom de l'image. Un suffixe :tag permet de ne
        sélectionner que les images correspondant au filtre qui ont le tag
        spécifié.
    rm filter
        Supprimer une ou plusieurs images
        Le filtre est une expression régulière de type awk qui est mise en
        correspondance avec le nom de l'image. Un suffixe :tag permet de ne
        sélectionner que les images correspondant au filtre qui ont le tag
        spécifié.
    X|prune
        Supprimer les containers et les images inutilisées

    composer|ci|cu|cr|cs [args...]
        Frontend pour lancer composer à l'intérieur d'un container. Les
        commandes 'ci', 'cu', 'cr', 'cs' sont des alias pour 'composer install',
        'composer update', 'composer rshell' et 'composer shell' respectivement
        S'il existe un fichier .composer.conf dans le répertoire du projet, il
        est sourcé. Ce fichier définit des variables qui indiquent comment la
        commande composer est lancée. Les variables suivantes peuvent être
        définies:
        * COMPOSER_PHP -- Version de PHP en dessous de laquelle COMPOSER_IMAGE
          est utilisé. En d'autres termes, c'est la version minimum de PHP
          nécessaire pour faire tourner composer. L'idée est que si la version
          de PHP installée est suffisante, il n'est pas nécessaire de passer par
          une image docker.
          Cette valeur doit être spécifiée avec le format de PHP_VERSION_ID i.e
          70300 pour PHP 7.3
          Spécifier 'any' ou 'force' pour forcer l'utilisation de l'image docker
        * COMPOSER_PHP_MAX -- Version de PHP à partir de laquelle COMPOSER_IMAGE
          est utilisée. En d'autres termes, c'est la version maximum de PHP, à
          partir de laquelle il faut passer par une image docker. L'idée est que
          si la version de PHP installée est trop récente, ça peut poser
          problème avec le calcul des dépendances.
          Cette valeur doit être spécifiée avec le format de PHP_VERSION_ID i.e
          70300 pour PHP 7.3
          Si la valeur n'est pas spécifiée ou vaut 'none', elle est ignorée.
        * COMPOSER_IMAGE -- Image utilisée pour lancer composer. La valeur par
          défaut est:
            $DEFAULT_COMPOSER_IMAGE
          Spécifier 'none' pour lancer directement composer sans passer par une
          image docker.
          L'image spécifiée doit disposer de la commande 'su-exec' afin de
          pouvoir lancer la commande avec l'utilisateur courant. Le répertoire
          \$HOME est monté à l'intérieur du container et le script composer.phar
          du projet est utilisé le cas échéant.
        * COMPOSER_MACHINE -- Nom de la docker machine sur laquelle se connecter
          pour lancer l'image docker. La valeur par défaut est -u, ce qui force
          l'utilisation de l'instance docker locale.
        * COMPOSER_CMD -- Chemin vers l'exécutable composer. Par défaut,
          utiliser composer.phar s'il existe dans le répertoire du projet. Sinon
          utiliser /usr/bin/composer
        * COMPOSER_SETUP -- Liste de commandes à lancer pour configurer le
          container. Dans ce cas, un container ayant pour base \$COMPOSER_IMAGE
          et nommé d'après le nom du projet est préparé et les commandes
          spécifiées y sont lancées. Ce container est réutilisé à chaque fois.
          Ce paramétrage est utilisé pour par exemple installer certains
          packages nécessaire au projet.
        La commande 'rshell' est une extension qui lance un shell bash au lieu
        de lancer la commande composer, ce qui permet de faire des opérations
        plus complexes si le besoin s'en fait sentir. NB: le shell est lancé
        avec l'utilisateur root. La commande alternative 'shell' lance le shell
        avec le compte utilisateur.
        Pour faciliter l'utilisation dans un script, les premiers arguments
        peuvent être utilisés pour redéfinir les variables COMPOSER_*, e.g
            $scriptname composer COMPOSER_IMAGE=none install

OPTIONS générales
    (ces options sont communes à toutes les commandes)
    -d, --chdir PROJDIR
        Spécifier le répertoire du projet
    -c, --config CONFIG[.yml]
        Spécifier le fichier de configuration à utiliser. le fichier de profil
        CONFIG.PROFILE.yml est chargé aussi s'il existe.
        NB: il n'est pas nécessaire d'ajouter l'extension .yml au nom, cela
        permet de faciliter l'utilisation avec l'auto-complétion quand il existe
        des fichiers de profil
        Si cette option n'est pas spécifiée, docker-compose.yml est utilisé par
        défaut (ou avec l'option --stack docker-stack.yml s'il existe)
    -p, --profile PROFILE
    -P, --prod
    -T, --test
        Spécifier le profil
    -m, --set-machine MACHINE
        Choisir l'environnement docker-machine spécifié avant de lancer les
        commandes. Utiliser -u pour desélectionner la machine en cours, e.g -m-u
    -s, --select-service SERVICE[.N]
        Sélectionner le service spécifié. Si le service a plusieurs instances,
        il est possible de sélectionner une instance en particulier avec le
        suffixe '.N' (par défaut, la première instance est sélectionnée)
        Quand un service est sélectionné, dans les commandes 'cp' et 'exec', la
        chaine 'SS' est remplacée par le nom du container correspondant e.g
            $scriptname -s X_Y exec SS bash
            $scriptname -s X_Y -- cp -L SS:path/to/file destdir
    -t, --select-machine SERVICE[.N]
        Sélectionner le service spécifié, puis choisir l'environnement
        docker-machine correspondant. Cette option a la priorité sur l'option
        --select-service
        Cette option est traitée après l'option --set-machine, ce qui permet de
        se connecter sur le manager d'un cluster puis de sélectionner le worker
        sur lequel tourne un service
    -n, --fake
        Ne pas lancer les commandes, simplement les afficher
    -e, --build-arg, --env VAR=VALUE
        (Re)définir un argument pour le build ou une variable d'environnement
    -f, --force
        Forcer l'opération (là où cela a du sens)
    -h, --host HOST
        Spécifier l'hôte pour la commande systemd-unit

OPTIONS build
    (ces options ne sont valides que pour les commandes build, brd, bs, bd, bpd)
    --stack
        Indiquer que le build est fait pour un déploiement avec deploy.
        S'il existe un fichier docker-stack.yml, il est utilisé de préférence à
        la place de docker-compose.yml. De même, les fichiers de profil de la
        forme docker-stack.PROFILE.yml sont utilisés de préférence aux fichiers
        docker-compose.PROFILE.yml
        Cette option n'est nécessaire qu'avec build puisque les commandes
        deploy, bd, bpd et update impliquent --stack
    -j, --no-cache
        Ne pas utiliser le cache lors du build
    -g, --ug, --no-update-apps
        ne pas mettre à jour les dépôts dépendants. ces dépôts sont définis dans
        le fichier update-apps.conf qui a le format suivant:
            DEFAULT_DEVEL_SRCDIR=
            DEFAULT_ORIGIN=
            DEFAULT_BRANCH=
            DEFAULT_COMPOSER_ACTION=
            PROFILE_CLEAN=        # fonction de nettoyage spécifique au profil
            CLEAN=                # ou... fonction de nettoyage par défaut
            APPS=()               # applications à mettre à jour par défaut
            PROFILE_APPS=()       # ou... spécifique au profil 'PROFILE'
            app_URL=              # url du dépôt
            app_DEVEL_SRCDIR=     # répertoire source du dépôt en mode devel
            app_SRC=              # répertoire/fichier source (si URL='')
            app_DEST=             # répertoire dans lequel faire le checkout
                                  # ou destination si synchro avec app_SRC
            app_NAME=             # nom du répertoire dest si checkout; par
                                  # défaut prendre la valeur 'app'
            app_RSYNC_OPTS=       # options de rsync si synchro (avec app_SRC
                                  # ou app_DEVEL_SRCDIR)
            app_PROFILE_ORIGIN=   # origine spécifique au profil 'PROFILE'
            app_ORIGIN=           # ou... origine par défaut de la branche
            app_PROFILE_BRANCH=   # branche spécifique au profil 'PROFILE'
            app_BRANCH=           # ou... branche par défaut
            app_TYPE=             # type de projet (composer|none)
            app_AFTER_UPDATE=()   # liste de commandes à lancer après le checkout
            app_COMPOSER_ACTION=  # action projet composer (install|update|none)
            app_COMPOSER_ARGS=()  # arguments de composer install|update
    -u, --uu, --update-apps-only
        Ne faire que la mise à jour depuis les dépôts dépendants.
    -w, --ww, --update-apps-devel
        Faire la mise à jour en mode devel: le projet ainsi que ses fichiers des
        répertoires vendor/lib et vendor/ur sont synchronisés via rsync depuis
        \$DEFAULT_DEVEL_SRCDIR qui vaut par défaut \$HOME/wop/php
    --uo, --update-apps-origin ORIGIN
        Spécifier l'origine par défaut pour update-apps
    --ub, --update-apps-branch BRANCH
        Spécifier la branche par défaut pour update-apps

OPTIONS deploy
    -l, --without-registry-auth
        Ne pas transporter les informations d'autorisation aux agents swarm
        (c'est à dire ne pas utiliser l'option --with-registry-auth)

VARIABLES de update-apps.conf
    SRC
        répertoire/fichier source, si URL n'est pas défini. si ce chemin est
        relatif, il doit être exprimé par rapport au répertoire du projet.
        IMPORTANT: dans ce cas, DEST n'est pas le répertoire de base du
        checkout, mais le répertoire destination pour la synchro
    DEVEL_SRCDIR
        répertoire source pour le mode devel. attention, il s'agit du répertoire
        du projet, alors que DEFAULT_DEVEL_SRCDIR est le répertoire de base par
        défaut des projets
    ORIGIN
        vaut 'origin' par défaut
    BRANCH
        vaut 'develop' par défaut
    TYPE
        vaut 'composer' par défaut si le fichier composer.json existe à la
        racine du projet. sinon vaut 'none' par défaut
    AFTER_UPDATE
        Cette variable est une liste de commandes à lancer après la maj du dépôt
        - si le chemin est absolu ou relatif, lancer la commande telle quelle
        - s'il n'y a pas de chemin, alors ce doit être le nom d'une fonction
          existante auquel on enlève le préfixe update_apps_func_
        Au moment où la commande est lancée, le répertoire courant est celui du
        projet. Les variables suivantes sont disponibles:
            URL=                  # url du dépôt
            DEVEL_SRCDIR=         # répertoire source en mode devel
            SRC=                  # répertoire/fichier source (si URL='')
            DEST=                 # répertoire dans lequel faire le checkout
                                  # ou destination si URL=''
            NAME=                 # nom du répertoire dest si checkout
            RSYNC_OPTS=           # options de rsync si synchro avec SRC ou
                                  # DEVEL_SRCDIR
            ORIGIN=               # origine de la branche
            BRANCH=               # branche sélectionnée dans le dépôt
            TYPE=                 # type de projet (composer|none)
    COMPOSER_ACTION
        vaut 'install' par défaut. Indique ce qu'il faut faire pour un projet de
        type 'composer' après avoir lancé les commandes de AFTER_UPDATE. Les
        directives supportées sont 'install', 'update' et 'none'
    COMPOSER_ARGS
        options à utiliser avec composer install|update. La valeur par défaut
        dépend du profil:
            prod:   --no-dev -o
            test:   --no-dev -o
            autres: (pas d'options)

FONCTIONS de update-apps.conf
    sqlmig [DESTDIR [SRCDIR [NOFIX]]]
        Copier les définitions des bases de données au format sqlmig de
        SRCDIR/config/sqlmig vers DESTDIR/config/mariadb/sqlmig
        Si SRCDIR ne se termine pas par '/config/sqlmig' ou si DESTDIR ne se
        termine pas par '/config/mariadb/sqlmig', rajouter ces suffixes
        automatiquement, sauf si une valeur NOFIX est spécifiée.
    pff [MAPS [PFFDIR]]
        Si PFFDIR est un projet pff, mettre à jour le profil pff en fonction du
        profil de déploiement.
        MAPS détermine le mapping entre profil de déploiement (prod, test,
        devel) et le profil pff. Il s'agit d'une liste de valeurs séparées par
        des virgules de la forme DEST[:SRC]
        - Une valeur de la forme 'DEST:SRC' fait correspondre le profil de
          déploiement SRC au profil pff 'DEST'
        - Une valeur de la forme 'DEST' force le choix du profil pff DEST quel
          que soit le profil de déploiement
        - Pour simplifier l'écriture des mappings, les aliases suivants sont
          reconnus:"'
            PTD        est remplacé par   prod:prod,test:test,devel:devel
            PT          "     "      "    prod:prod,test:test
        '"Si aucun profil de déploiement ne correspond, le comportement par défaut
        est de forcer le premier profil défini dans le projet pff
    mvn [ARGS]
        Lancer maven dans le répertoire destination avec les arguments spécifiés"
}

function echo_lines() { local IFS=$'\n'; echo "$*"; }

function get_version() {
    local GIT_DIR; unset GIT_DIR
    if git rev-parse --git-dir >/dev/null 2>&1; then
        local head commit tag
        commit="$(git rev-list --tags --max-count=1 2>/dev/null)"
        if [ -n "$commit" ]; then
            tag="$(git describe --tags "$commit" 2>/dev/null)"
            if [ -n "$tag" ]; then
                head="$(git rev-parse HEAD)"
                if [ "$commit" != "$head" ]; then
                    echo "$tag-develop"
                else
                    echo "$tag"
                fi
                return
            fi
        fi
    elif [ -f VERSION.txt ]; then
        cat VERSION.txt
    fi
    echo develop
}

function docker_add_build_arg() {
    eval "replace_build_args+=(--build-arg $1=\"$2\"); $1=\"$2\""
}
function docker_parse_build_args() {
    cat "$1" |
    grep -v '^#' |
    grep -v '^$' |
    sed -r 's/([^=]+)=(.*)/\1=\2; replace_build_args+=(--build-arg \1="$\1")/'
}
function docker_parse_env_args() {
    [ -f .build.env ] && eval "$(docker_parse_build_args .build.env)"
    [ -f build.env ] && eval "$(docker_parse_build_args build.env)"
    [ -n "$PROFILE" -a -f ".build.$PROFILE.env" ] && eval "$(docker_parse_build_args ".build.$PROFILE.env")"
    [ -n "$PROFILE" -a -f "build.$PROFILE.env" ] && eval "$(docker_parse_build_args "build.$PROFILE.env")"
}
function docker_set_env_args() {
    [ -f .build.env ] && source ./.build.env
    [ -f build.env ] && source ./build.env
    [ -n "$PROFILE" -a -f ".build.$PROFILE.env" ] && source "./.build.$PROFILE.env"
    [ -n "$PROFILE" -a -f "build.$PROFILE.env" ] && source "./build.$PROFILE.env"
}
function docker_set_run_args() {
    replace_run_args+=(--env-file "$1")
    source "$1"
}
function docker_check_name() {
    [ -n "$NAME" ] || die "Vous devez définir NAME dans .build.env"

    if [ "$1" == set_container_name ]; then
        project_name="$NAME"
        container_name="${project_name//[^a-zA-Z0-9_-]/}"
        [ -n "$PROFILE" ] && container_name="${container_name}_$PROFILE"
    fi
}

function compose_set_project_name() {
    local PROJECT_NAME= PROJECT_NAME_REMOVE_SUFFIX=--defaults-- PROJECT_NAME_ADD_PROFILE=
    [ -f .compose.env ] && source ./.compose.env

    [ -n "$PROJECT_NAME" ] || PROJECT_NAME="$(basename -- "$(pwd)")"
    if [ "$PROJECT_NAME_REMOVE_SUFFIX" == --defaults-- ]; then
        if [ "${PROJECT_NAME%.service}" != "$PROJECT_NAME" ]; then PROJECT_NAME="${PROJECT_NAME%.service}"
        elif [ "${PROJECT_NAME%.stack}" != "$PROJECT_NAME" ]; then PROJECT_NAME="${PROJECT_NAME%.stack}"
        elif [ "${PROJECT_NAME%.network}" != "$PROJECT_NAME" ]; then PROJECT_NAME="${PROJECT_NAME%.network}"
        fi
    else
        PROJECT_NAME="${PROJECT_NAME%$PROJECT_NAME_REMOVE_SUFFIX}"
    fi

    if [ -n "$PROFILE" ]; then
        [ -n "$COMPOSE_PROJECT_NAME" ] || COMPOSE_PROJECT_NAME="$PROJECT_NAME${PROJECT_NAME_ADD_PROFILE:+_${PROFILE}}"
    else
        [ -n "$COMPOSE_PROJECT_NAME" ] || COMPOSE_PROJECT_NAME="$PROJECT_NAME"
    fi
    export COMPOSE_PROJECT_NAME

    if [ "$1" == set_container_name ]; then
        project_name="$PROJECT_NAME"
        container_name="${project_name//[^a-zA-Z0-9_-]/}"
        [ -n "$PROFILE" -a -n "$PROJECT_NAME_ADD_PROFILE" ] && container_name="${container_name}_$PROFILE"
    fi
}
function compose_set_env_args() {
    if [ -n "$CONFIG" ]; then
        # autocomplétion friendly
        [ ! -f "$CONFIG" -a -f "${CONFIG}yml" ] && CONFIG="${CONFIG}yml"
        [ ! -f "$CONFIG" -a -f "$CONFIG.yml" ] && CONFIG="$CONFIG.yml"
        ##
        replace_env_args+=(-f "$CONFIG")
        if [ -n "$PROFILE" -a -f "${CONFIG%.yml}.$PROFILE.yml" ]; then
            replace_env_args+=(-f "${CONFIG%.yml}.$PROFILE.yml")
        fi
    else
        if [ -n "$USE_STACK" -a -f docker-stack.yml ]; then
            replace_env_args+=(-f docker-stack.yml)
        else
            replace_env_args+=(-f docker-compose.yml)
        fi
        if [ -n "$USE_STACK" -a -f docker-stack.override.yml ]; then
            replace_env_args+=(-f docker-stack.override.yml)
        elif [ -f docker-compose.override.yml ]; then
            replace_env_args+=(-f docker-compose.override.yml)
        fi
        if [ -n "$PROFILE" ]; then
            if [ -n "$USE_STACK" -a -f "docker-stack.$PROFILE.yml" ]; then
                replace_env_args+=(-f "docker-stack.$PROFILE.yml")
            elif [ -f "docker-compose.$PROFILE.yml" ]; then
                replace_env_args+=(-f "docker-compose.$PROFILE.yml")
            fi
        fi
    fi

    compose_set_project_name "$@"
}
function docker_set_deploy_args() {
    if [ -n "$CONFIG" ]; then
        # autocomplétion friendly
        [ ! -f "$CONFIG" -a -f "${CONFIG}yml" ] && CONFIG="${CONFIG}yml"
        [ ! -f "$CONFIG" -a -f "$CONFIG.yml" ] && CONFIG="$CONFIG.yml"
        ##
        replace_deploy_args+=(-c "$CONFIG")
        if [ -n "$PROFILE" -a -f "${CONFIG%.yml}.$PROFILE.yml" ]; then
            replace_deploy_args+=(-c "${CONFIG%.yml}.$PROFILE.yml")
        fi
    else
        if [ -n "$USE_STACK" -a -f docker-stack.yml ]; then
            replace_deploy_args+=(-c docker-stack.yml)
        else
            replace_deploy_args+=(-c docker-compose.yml)
        fi
        if [ -n "$USE_STACK" -a -f docker-stack.override.yml ]; then
            replace_deploy_args+=(-c docker-stack.override.yml)
        elif [ -f docker-compose.override.yml ]; then
            replace_deploy_args+=(-c docker-compose.override.yml)
        fi
        if [ -n "$PROFILE" ]; then
            if [ -n "$USE_STACK" -a -f "docker-stack.$PROFILE.yml" ]; then
                replace_deploy_args+=(-c "docker-stack.$PROFILE.yml")
            elif [ -f "docker-compose.$PROFILE.yml" ]; then
                replace_deploy_args+=(-c "docker-compose.$PROFILE.yml")
            fi
        fi
    fi

    compose_set_project_name "$@"
}

function host_run() {
    # lancer le script $2..@ sur l'hôte $1
    # si $1 n'est pas défini ou est le nom de l'hôte local ou vaut 'localhost',
    # lancer le script en local avec les droits root
    # sinon, si docker-machine existe, l'hôte doit correspondre à la machine active
    # sinon, lancer inconditionnellement le script sur l'hôte distant
    local host="$1" script="$2"; shift; shift
    if [ -n "$host" -a "$host" != localhost ]; then
        if check_hostname "$host"; then
            estep "Lancement de $script localement"
            runscript_as_root "$script" "$@"
        elif progexists docker-machine; then
            if [ "${host%%.*}" == "$DOCKER_MACHINE_NAME" ]; then
                estep "Copie du script vers root@$host"
                scp "$script" "root@$host:/tmp/tmp-dk-service-script" || die
                estep "Lancement du script à distance"
                local -a args; args=(/tmp/tmp-dk-service-script "$@")
                ssh -qt "root@$host" "$(qvals "${args[@]}"); rm -f /tmp/tmp-dk-service-script"
            else
                ewarn "La machine active ($DOCKER_MACHINE_NAME) n'est pas la destination ($host)"
            fi
        else
            estep "Copie du script vers root@$host"
            scp "$script" "root@$host:/tmp/tmp-dk-service-script" || die
            estep "Lancement du script à distance"
            local -a args; args=(/tmp/tmp-dk-service-script "$@")
            ssh -qt "root@$host" "$(qvals "${args[@]}"); rm -f /tmp/tmp-dk-service-script"
        fi
    else
        estep "Lancement de $script localement"
        runscript_as_root "$script" "$@"
    fi
}
function local_run() {
    # lancer le script $2..@ sur l'hôte $1 uniquement si c'est l'hôte courant
    local host="$1" script="$2"; shift; shift
    if [ -n "$host" ]; then
        if ! check_hostname "$host"; then
            eerror "Cette commande doit obligatoirement être lancée depuis l'hôte $host"
            return 1
        fi
    fi
    estep "Lancement de $script localement"
    runscript_as_root "$script" "$@"
}

BUILD_UPDATE_APPS=
BUILD_BUILD=
UPDATE_APPS_ORIGIN=
UPDATE_APPS_BRANCH=
function build_set_options() {
    BUILD_BUILD=
    BUILD_UPDATE_APPS=
    BUILD_UPDATE_DEVEL=
    [[ "$1" == *b* ]] && BUILD_BUILD=1
    [[ "$1" == *u* ]] && BUILD_UPDATE_APPS=1
    [[ "$1" == *w* ]] && BUILD_UPDATE_DEVEL=1
    UPDATE_APPS_ORIGIN="$2"
    UPDATE_APPS_BRANCH="$3"
}

function update_apps_func_sqlmig() {
    local destdir="$1" srcdir="$2" nofix="$3"
    [ -n "$destdir" ] || destdir=db
    [ -n "$srcdir" ] || srcdir="$DEST"

    if [ -z "$nofix" ]; then
        [ "${destdir%/config/mariadb/sqlmig}" != "$destdir" ] || destdir="$destdir/config/mariadb/sqlmig"
        [ "${srcdir%/config/sqlmig}" != "$srcdir" ] || srcdir="$srcdir/config/sqlmig"
    fi

    if [ ! -d "$srcdir" ]; then
        eerror "$srcdir: répertoire introuvable"
        return 1
    fi

    local -a sqlmigs; local sqlmig name
    array_lsall sqlmigs "$srcdir"
    mkdir -p "$destdir" || return 1
    for sqlmig in "${sqlmigs[@]}"; do
        if [ -d "$sqlmig" ]; then
            setx name=basename -- "$sqlmig"
            rsync -av --delete-after "$sqlmig/" "$destdir/$name"
        else
            rsync -av "$sqlmig" "$destdir"
        fi
    done
    return 0
}

function __maps_replace() {
    if [ "${maps#$1,}" != "$maps" ]; then
        maps="$2,${maps#$1,}"
    elif [ "${maps%,$1}" != "$maps" ]; then
        maps="${maps%,$1},$2"
    elif [ "${maps/,$1,/}" != "$maps" ]; then
        maps="${maps/,$1,/,$2,}"
    elif [ "$maps" == "$1" ]; then
        maps="$2"
    fi
}

function update_apps_func_pff() {
    local maps="$1" pffdir="${2:-$DEST}"
    [ -f "$pffdir/$PFF_CONF" ] || return 0

    # aliases
    __maps_replace PTD prod:prod,test:test,devel:devel
    __maps_replace PT prod:prod,test:test

    source "$pffdir/$PFF_CONF"
    pff_autofix "$pffdir"

    local map src dest
    array_split maps "$maps" ,
    for map in "${maps[@]}"; do
        if [ -z "$map" ]; then
            continue
        elif [[ "$map" == *:* ]]; then
            # mapping de profil
            splitpair "$map" dest src
            if [ "$src" == "$PROFILE" ]; then
                if array_contains PROFILES "$dest"; then
                    estep "Sélection du profil pff $dest"
                    pff_select_profile "$dest" "$pffdir"
                    return 0
                fi
                eerror "$dest: profil invalide, il a été ignoré"
            fi
        else
            # forcer le profil
            dest="$map"
            if array_contains PROFILES "$dest"; then
                estep "Sélection du profil pff $dest"
                pff_select_profile "$dest" "$pffdir"
                return 0
            fi
            eerror "$dest: profil invalide, il a été ignoré"
        fi
    done
    # sélectionner le premier profil
    setx dest=pff_get_first_profile "$pffdir"
    if [ -n "$dest" ]; then
        estep "Sélection du profil pff $dest"
        pff_select_profile "$dest" "$pffdir"
    fi
}

function update_apps_func_mvn() {
    local cwd="$(pwd)"
    cd "$DEST"
    mvn "$@" || die
    cd "$cwd"
}

function build_update_apps() {
    [ -n "$BUILD_UPDATE_APPS" ] || return 0
    [ -f update-apps.conf ] || return 0

    # charger le fichier de configuration
    local DEFAULT_ORIGIN DEFAULT_BRANCH APPS
    DEFAULT_ORIGIN="$UPDATE_APPS_ORIGIN"
    [ -z "$DEFAULT_ORIGIN" ] && DEFAULT_ORIGIN=origin
    DEFAULT_BRANCH="$UPDATE_APPS_BRANCH"
    #XXX à terme, ne déployer en prod que la branche master
    [ -z "$DEFAULT_BRANCH" -a "$PROFILE" == prod ] && DEFAULT_BRANCH=develop #XXX master
    [ -z "$DEFAULT_BRANCH" ] && DEFAULT_BRANCH=develop
    DEFAULT_DEVEL_SRCDIR="$HOME/wop/php"
    DEFAULT_COMPOSER_ACTION=install
    APPS=()
    CLEAN=
    [ -f update-apps.conf ] && source ./update-apps.conf

    local apps # liste des applications spécifique au profil
    apps="${PROFILE}_APPS[@]"; apps=("${!apps}")
    [ ${#apps[*]} -gt 0 ] && APPS=("${apps[@]}")
    [ ${#APPS[*]} -gt 0 ] || return 0

    local PRODUCTION DEVELOPMENT
    case "$PROFILE" in
    prod) PRODUCTION=1; DEVELOPMENT=; is_defined DEFAULT_COMPOSER_ARGS || DEFAULT_COMPOSER_ARGS=(--no-dev -o);;
    test) PRODUCTION=1; DEVELOPMENT=1; is_defined DEFAULT_COMPOSER_ARGS || DEFAULT_COMPOSER_ARGS=(--no-dev -o);;
    devel) PRODUCTION=; DEVELOPMENT=1; is_defined DEFAULT_COMPOSER_ARGS || DEFAULT_COMPOSER_ARGS=();;
    esac

    local clean
    clean="${PROFILE}_CLEAN"; clean="${!clean}"
    [ -n "$clean" ] && CLEAN="$clean"
    if [ -n "$CLEAN" ]; then
        etitle "Nettoyage"
        "$CLEAN"
        eend
    fi

    etitle "Mise à jour des dépendances"
    local app var URL SRC DEVEL_SRCDIR DEST NAME have_RSYNC_OPTS RSYNC_OPTS ORIGIN BRANCH TYPE after_update after_updates composer_action
    local -a composer_args
    for app in "${APPS[@]}"; do
        etitle "$app"

        var="${app//-/_}"

        URL="${var}_URL"; URL="${!URL}"
        if [ -z "$URL" ]; then
            SRC="${var}_SRC"; SRC="${!SRC}"
            if [ -z "$SRC" ]; then
                ewarn "$app: vous devez définir ${var}_URL ou ${var}_SRC"
                eend; return 1
            fi
        fi

        DEVEL_SRCDIR="${var}_DEVEL_SRCDIR"; DEVEL_SRCDIR="${!DEVEL_SRCDIR}"
        [ -n "$DEVEL_SRCDIR" ] || DEVEL_SRCDIR="$DEFAULT_DEVEL_SRCDIR/${URL##*/}"

        DEST="${var}_DEST"; DEST="${!DEST}"
        [ -n "$DEST" ] || DEST="$app/b"

        NAME="${var}_NAME"; NAME="${!NAME}"
        [ -n "$NAME" ] || NAME="$app"

        if is_defined "${var}_RSYNC_OPTS"; then
            have_RSYNC_OPTS=1
            RSYNC_OPTS="${var}_RSYNC_OPTS[@]"; RSYNC_OPTS=("${!RSYNC_OPTS}")
        else
            have_RSYNC_OPTS=
            RSYNC_OPTS=()
        fi

        ORIGIN="${var}_${PROFILE}_ORIGIN"; ORIGIN="${!ORIGIN}"
        [ -n "$ORIGIN" ] || { ORIGIN="${var}_ORIGIN"; ORIGIN="${!ORIGIN}"; }
        [ -n "$ORIGIN" ] || ORIGIN="$DEFAULT_ORIGIN"

        BRANCH="${var}_${PROFILE}_BRANCH"; BRANCH="${!BRANCH}"
        [ -n "$BRANCH" ] || { BRANCH="${var}_BRANCH"; BRANCH="${!BRANCH}"; }
        [ -n "$BRANCH" ] || BRANCH="$DEFAULT_BRANCH"

        # calculer le type maintenant, on en a besoin pour le mode devel
        TYPE="${var}_TYPE"; TYPE="${!TYPE}"

        if [ -n "$BUILD_UPDATE_DEVEL" ]; then
            mkdir -p "$DEST" || { eend; return 1; }
            DEST="$DEST/$NAME"

            # synchronisation en mode devel
            [ -n "$have_RSYNC_OPTS" ] || RSYNC_OPTS=(--delete --delete-excluded)
            local -a rsync_opts; rsync_opts=(-a --exclude .git/ "${RSYNC_OPTS[@]}")
            estep "Synchro $DEVEL_SRCDIR --> $DEST"
            rsync "${rsync_opts[@]}" "$DEVEL_SRCDIR/" "$DEST" || { eend; return 1; }

            if [ -z "$TYPE" ]; then
                # possible de détecter le type quand on a le projet
                # en cas de maj ici, mettre à jour aussi le code ci-dessous
                if [ -f "$DEST/composer.json" ]; then TYPE=composer
                else TYPE=none
                fi
            fi

            if [ "$TYPE" == composer ]; then
                # Synchronisation des dépendances
                local -a depdirs; local depdir pname
                setx -a depdirs=ls -d "$DEST/vendor/"{lib,ur}/* 2>/dev/null
                for depdir in "${depdirs[@]}"; do
                    [ -L "$depdir" ] && rm "$depdir"
                    pname="${depdir#$DEST/vendor/}"; pname="${pname/\//-}"
                    estep "Synchro $DEFAULT_DEVEL_SRCDIR/$pname --> $depdir"
                    rsync "${rsync_opts[@]}" --exclude /vendor/ "$DEFAULT_DEVEL_SRCDIR/$pname/" "$depdir"
                done
            fi
        elif [ -n "$URL" ]; then
            mkdir -p "$DEST" || { eend; return 1; }
            DEST="$DEST/$NAME"

            if [ -d "$DEST" -a -d "$DEST/.git" ]; then
                # mise à jour
                estep "Maj dépôt $URL:$BRANCH --> $DEST"
                setx cwd=pwd
                cd "$DEST"
                git fetch --all -p -f || { eend; return 1; }
                git reset --hard "$ORIGIN/$BRANCH" || { eend; return 1; }
                cd "$cwd"
            else
                # reliquat mode devel?
                [ -d "$DEST" ] && rm -rf "$DEST"
                # clonage initial
                estep "Clonage $URL:$BRANCH --> $DEST"
                git clone -o "$ORIGIN" -b "$BRANCH" "$URL" "$DEST" || { eend; return 1; }
            fi

            if [ -z "$TYPE" ]; then
                # possible de détecter le type quand on a le projet
                # en cas de maj ici, mettre à jour aussi le code ci-dessus et ci-dessous
                if [ -f "$DEST/composer.json" ]; then TYPE=composer
                else TYPE=none
                fi
            fi

        elif [ -n "$SRC" ]; then
            if [ -d "$SRC" ]; then
                [ -n "$have_RSYNC_OPTS" ] || RSYNC_OPTS=(--delete --delete-excluded)
                local -a rsync_opts; rsync_opts=(-a --exclude .git/ "${RSYNC_OPTS[@]}")

                SRC="${SRC%/}/"
                DEST="${DEST%/}"
                if [ ! -d "$DEST" ]; then
                    # s'assurer que le répertoire parent existe
                    mkdir -p "$(dirname -- "$DEST")" || { eend; return 1; }
                fi
                DEST="$DEST/"

                estep "Synchro $SRC --> $DEST"
                rsync "${rsync_opts[@]}" "$SRC" "$DEST" || { eend; return 1; }

            elif [ -f "$SRC" ]; then
                [ -n "$have_RSYNC_OPTS" ] || RSYNC_OPTS=()
                local -a rsync_opts; rsync_opts=(-a "${RSYNC_OPTS[@]}")

                if [ "${DEST%/}" != "$DEST" -a ! -d "$DEST" ]; then
                    # on demande à ce que DEST soit un répertoire mais il
                    # n'existe pas: le créer
                    DEST="${DEST%/}"
                    mkdir -p "$DEST" || { eend; return 1; }
                fi

                if [ -d "$DEST" ]; then
                    DEST="$DEST/"
                elif [ ! -f "$DEST" ]; then
                    # la destination n'existe pas: s'assurer que le répertoire
                    # parent existe
                    mkdir -p "$(dirname -- "$DEST")" || { eend; return 1; }
                fi
                estep "Synchro $SRC --> $DEST"
                rsync "${rsync_opts[@]}" "$SRC" "$DEST" || { eend; return 1; }

            else
                eerror "$app: $SRC: répertoire/fichier introuvable"
                eend; return 1
            fi

            if [ -z "$TYPE" ]; then
                # possible de détecter le type quand on a le projet
                # en cas de maj ici, mettre à jour aussi le code ci-dessus
                if [ -f "$DEST/composer.json" ]; then TYPE=composer
                else TYPE=none
                fi
            fi

        else
            # ne devrait pas se produire
            die "ni URL ni SRC ne sont définis"
        fi

        after_updates="${var}_AFTER_UPDATE"
        if is_defined "$after_updates"; then
            after_updates="$after_updates[@]"; after_updates=("${!after_updates}")
        elif [ "$TYPE" == composer ]; then
            after_updates=(sqlmig)
        else
            after_updates=()
        fi

        estep "Type de dépôt: $TYPE"
        if [ "$TYPE" == composer ]; then
            composer_action="${var}_${PROFILE}_COMPOSER_ACTION"; composer_action="${!composer_action}"
            [ -n "$composer_action" ] || { composer_action="${var}_COMPOSER_ACTION"; composer_action="${!composer_action}"; }
            [ -n "$composer_action" ] || composer_action="$DEFAULT_COMPOSER_ACTION"

            composer_args="${var}_${PROFILE}_COMPOSER_ARGS"
            is_defined "$composer_args" || composer_args="${var}_COMPOSER_ARGS"
            is_defined "$composer_args" || composer_args="DEFAULT_COMPOSER_ARGS"
            composer_args="${composer_args}[@]"; composer_args=("${!composer_args}")

            if [ -z "$BUILD_UPDATE_DEVEL" ]; then
                case "${composer_action:-install}" in
                i|install) composer_action=install;;
                u|update) composer_action=update;;
                none|nop) composer_action=;;
                *) ewarn "$composer_action: action invalide"; composer_action=;;
                esac
                if [ -n "$composer_action" ]; then
                    setx cwd=pwd
                    cd "$DEST"
                    estep "Installation des dépendances composer"
                    auto_composer "$composer_action" "${composer_args[@]}" || { eend; return 1; }
                    cd "$cwd"
                fi
            fi
        fi

        for after_update in "${after_updates[@]}"; do
            if [ "${after_update#/}" != "$after_update" ]; then
                # commande absolue, la lancer telle quelle
                etitle "$after_update"
                eval "$after_update" || { eend; eend; return 1; }
                eend
            elif [ "${after_update#./}" != "$after_update" ]; then
                # commande relative, la lancer telle quelle
                etitle "$after_update"
                eval "$after_update" || { eend; eend; return 1; }
                eend
            else
                # c'est une fonction update_apps_func_*
                etitle "$after_update"
                eval "update_apps_func_$after_update" || { eend; eend; return 1; }
                eend
            fi
        done

        eend
    done
    eend
}

function initialize_build_env() {
    CTXDIR=.
    NAME=
    TAGS=(latest)
    VERSION=
    DIST=
}
function default_update_build_env() {
    local var
    if [ -n "$DIST" ]; then
        [ -n "$VERSION" ] && VERSION="$VERSION-"
        VERSION="$VERSION$DIST"
        TAGS+=("$DIST")
    fi
    [ -n "$VERSION" ] || docker_add_build_arg VERSION "$(get_version)"
    [ -n "$VERSION" ] && TAGS+=("$VERSION")
    # Variables en ligne de commande
    for var in "${VARS[@]}" "$@"; do
        docker_add_build_arg "${var%%=*}" "${var#*=}"
    done
}
function update_build_env() { default_update_build_env "$@"; }

function default_compose_build() {
    ${FAKE:+qvals} docker-compose \
        "${replace_env_args[@]}" "${env_args[@]}" \
        build \
        ${NO_CACHE:+--no-cache} \
        "${replace_build_args[@]}" "${build_args[@]}" \
        "$@"
}
function default_docker_build() {
    local tag
    for tag in "${TAGS[@]}"; do
        replace_build_args+=(-t "$NAME:$tag")
    done
    ${FAKE:+qvals} "$DOCKER" build \
        ${NO_CACHE:+--no-cache} \
        "${replace_env_args[@]}" "${env_args[@]}" \
        "${replace_build_args[@]}" "${build_args[@]}" \
        "$@" "$CTXDIR"
}
function compose_build() {
    [ -n "$BUILD_BUILD" ] || return 0
    default_compose_build "$@"
}
function docker_build() {
    [ -n "$BUILD_BUILD" ] || return 0
    default_docker_build "$@"
}
function __parse_bargs() {
    bargs=()
    args=()
    while [ $# -gt 0 ]; do
        [[ "$1" == *=* ]] && bargs+=("$1") || args+=("$1")
        shift
    done
}
function auto_build() {
    local -a replace_env_args env_args
    local -a replace_build_args build_args
    initialize_build_env

    local -a args bargs; local barg
    __parse_bargs "$@"
    set -- "${args[@]}"

    if [ -f docker-compose.yml ]; then
        compose_set_env_args
        update_build_env "${bargs[@]}"
        build_update_apps || return 1
        compose_build "$@"
    elif [ -f Dockerfile ]; then
        docker_parse_env_args
        docker_check_name
        docker_add_build_arg build_date "$(date +%y%m%d)"
        update_build_env "${bargs[@]}"
        build_update_apps || return 1
        docker_build "$@"
    else
        for barg in "${bargs[@]}"; do
            replace_build_args+=(--build-arg "$barg")
        done
        ${FAKE:+qvals} "$DOCKER" build \
            ${NO_CACHE:+--no-cache} \
            "${replace_build_args[@]}" "$@"
    fi
}

function default_compose_push() {
    ${FAKE:+qvals} docker-compose \
        "${replace_env_args[@]}" "${env_args[@]}" \
        push \
        "$@"
}
function default_docker_push() {
    ${FAKE:+qvals} "$DOCKER" push \
        "${replace_env_args[@]}" "${env_args[@]}" \
        "$@"
}
function compose_push() {
    default_compose_push "$@"
}
function docker_push() {
    local tag
    for tag in "${TAGS[@]}"; do
        default_docker_push "$NAME:$tag" "$@"
    done
}
function auto_push() {
    local -a replace_env_args env_args
    local -a replace_build_args build_args
    initialize_build_env
    if [ -f docker-compose.yml ]; then
        compose_set_env_args
        update_build_env
        compose_push "$@"
    elif [ -f Dockerfile ]; then
        docker_parse_env_args
        docker_check_name
        update_build_env
        docker_push "$@"
    else
        ${FAKE:+qvals} "$DOCKER" push "$@"
    fi
}

function default_compose_up() {
    ${FAKE:+qvals} docker-compose \
        "${replace_env_args[@]}" "${env_args[@]}" \
        up "${replace_run_args[@]}" "${run_args[@]}" \
        "${replace_user_args[@]}" "${user_args[@]}" "$@"
}
function default_docker_up() {
    ${FAKE:+qvals} "$DOCKER" run \
           "${replace_env_args[@]}" "${env_args[@]}" \
           "${replace_run_args[@]}" "${run_args[@]}" \
           "$NAME" \
           "${replace_user_args[@]}" "${user_args[@]}" "$@"
}
function compose_up() { default_compose_up "$@"; }
function docker_up() { default_docker_up "$@"; }
function auto_up() {
    local -a replace_env_args env_args
    local -a replace_run_args run_args
    local -a replace_user_args user_args
    local project_name container_name
    if [ -f docker-compose.yml ]; then
        compose_set_env_args
        replace_run_args=(-d)
        compose_up "$@"
    elif [ -f Dockerfile ]; then
        docker_set_env_args
        docker_check_name set_container_name
        replace_run_args=(-d --name "$container_name")
        docker_up "$@"
    else
        ${FAKE:+qvals} "$DOCKER" run "$@"
    fi
}

function default_compose_stop() {
    ${FAKE:+qvals} docker-compose \
        "${replace_env_args[@]}" "${env_args[@]}" \
        stop "${replace_stop_args[@]}" "${stop_args[@]}" \
        "$@"
}
function default_docker_stop() {
    ${FAKE:+qvals} "$DOCKER" container stop \
           "${replace_stop_args[@]}" "${stop_args[@]}" \
           "$container_name" "$@"
}
function compose_stop() { default_compose_stop "$@"; }
function docker_stop() { default_docker_stop "$@"; }
function auto_stop() {
    local -a replace_env_args env_args
    local -a replace_stop_args stop_args
    local project_name container_name
    if [ -f docker-compose.yml ]; then
        compose_set_env_args
        compose_stop "$@"
    elif [ -f Dockerfile ]; then
        docker_set_env_args
        docker_check_name set_container_name
        docker_stop "$@"
    else
        ${FAKE:+qvals} "$DOCKER" stop "$@"
    fi
}

function default_compose_logs() {
    ${FAKE:+qvals} docker-compose \
        "${replace_env_args[@]}" "${env_args[@]}" \
        logs "${replace_logs_args[@]}" "${logs_args[@]}" \
        "$@"
}
function default_docker_logs() {
    ${FAKE:+qvals} "$DOCKER" logs \
           "${replace_logs_args[@]}" "${logs_args[@]}" \
           "$container_name" "$@"
}
function compose_logs() { default_compose_logs "$@"; }
function docker_logs() { default_docker_logs "$@"; }
function auto_logs() {
    local -a replace_env_args env_args
    local -a replace_logs_args logs_args
    local project_name container_name
    if [ -f docker-compose.yml ]; then
        compose_set_env_args
        replace_logs_args=(-f)
        compose_logs "$@"
    elif [ -f Dockerfile ]; then
        docker_set_env_args
        docker_check_name set_container_name
        replace_logs_args=(-f)
        docker_logs "$@"
    else
        ${FAKE:+qvals} "$DOCKER" logs "$@"
    fi
}

function default_compose_down() {
    ${FAKE:+qvals} docker-compose \
        "${replace_env_args[@]}" "${env_args[@]}" \
        down "${replace_down_args[@]}" "${down_args[@]}" \
        "$@"
}
function default_docker_down() {
    estep "stop"
    ${FAKE:+qvals} "$DOCKER" container stop \
           "${replace_down_args[@]}" "${down_args[@]}" \
           "$container_name" "$@"
    estep "rm"
    ${FAKE:+qvals} "$DOCKER" container rm \
           "${replace_rm_args[@]}" "${rm_args[@]}" \
           "$container_name"
}
function compose_down() { default_compose_down "$@"; }
function docker_down() { default_docker_down "$@"; }
function auto_down() {
    local -a replace_env_args env_args
    local -a replace_down_args down_args
    local -a replace_rm_args rm_args
    local project_name container_name
    if [ -f docker-compose.yml ]; then
        compose_set_env_args
        compose_down "$@"
    elif [ -f Dockerfile ]; then
        docker_set_env_args
        docker_check_name set_container_name
        docker_down "$@"
    else
        ${FAKE:+qvals} "$DOCKER" stop "$@"
    fi
}

function default_compose_run() {
    ${FAKE:+qvals} docker-compose \
        "${replace_env_args[@]}" "${env_args[@]}" \
        run "${replace_run_args[@]}" "${run_args[@]}" \
        "${replace_user_args[@]}" "${user_args[@]}" "$@"
}
function default_docker_run() {
    ${FAKE:+qvals} "$DOCKER" run \
           "${replace_env_args[@]}" "${env_args[@]}" \
           "${replace_run_args[@]}" "${run_args[@]}" \
           "$NAME" \
           "${replace_user_args[@]}" "${user_args[@]}" "$@"
}
function compose_run() { default_compose_run "$@"; }
function docker_run() { default_docker_run "$@"; }
function auto_run() {
    eval "$(utools_local parse_opts)"
    local detach no_deps rm=1 service_ports use_aliases volume disable_tty
    local -a envs labels
    local var
    for var in "${VARS[@]}"; do
        envs+=(-e "$var")
    done

    args=(
        +
        -d,--detach detach=1
        -e: '$add@ envs -e; add@ envs'
        -l:,--label: '$add@ labels -l; add@ labels'
        --no-deps no_deps=1
        --no-rm rm=
        --rm rm=1
        --service-ports service_ports=1
        --use-aliases use_aliases=1
        -v:,--volume: volume=
        -T disable_tty=1
        @ args -- "$@"
    )
    parse_opts "${args[@]}" && set -- "${args[@]}" || { eerror "$args"; return 1; }

    local -a replace_env_args env_args
    local -a replace_run_args run_args
    local -a replace_user_args user_args
    local project_name container_name
    if [ -f docker-compose.yml ]; then
        compose_set_env_args
        replace_run_args=(
            ${detach:+-d}
            "${envs[@]}" "${labels[@]}"
            ${no_deps:+--no-deps}
            ${rm:+--rm}
            ${service_ports:+--service-ports}
            ${use_aliases:+--use-aliases}
            ${volume:+-v "$volume"}
            ${disable_tty:+-T}
        )
        compose_run "$@"
    elif [ -f Dockerfile ]; then
        docker_set_env_args
        docker_check_name set_container_name
        replace_run_args=(
            --name "$container_name"
            ${rm:+--rm}
            ${volume:+-v "$volume"}
        )
        docker_run "$@"
    else
        ${FAKE:+qvals} "$DOCKER" run ${rm:+--rm} ${volume:+-v "$volume"} "$@"
    fi
}

function default_compose_exec() {
    ${FAKE:+qvals} docker-compose \
        "${replace_env_args[@]}" "${env_args[@]}" \
        exec "${replace_exec_args[@]}" "${exec_args[@]}" \
        "$@"
}
function default_docker_exec() {
    ${FAKE:+qvals} "$DOCKER" container exec \
           "${replace_exec_args[@]}" "${exec_args[@]}" \
           "$container_name" "$@"
}
function compose_exec() { default_compose_exec "$@"; }
function docker_exec() { default_docker_exec "$@"; }
function auto_exec() {
    local -a replace_env_args env_args
    local -a replace_exec_args exec_args
    local project_name container_name
    if [ -f docker-compose.yml ]; then
        compose_set_env_args
        local container="$1"; shift
        if [ -n "$SELECT_CONTAINER" ]; then
            [ -z "$container" -o "$container" == SS ] && container="$SELECT_CONTAINER"
        fi
        local command="$1"; shift
        [ -n "$command" ] || command=bash
        compose_exec "$container" "$command" "$@"
    elif [ -f Dockerfile ]; then
        docker_set_env_args
        docker_check_name set_container_name
        exec_args=(-it)
        local command="$1"; shift
        [ -n "$command" ] || command=bash
        docker_exec "$command" "$@"
    else
        local container="$1"; shift
        if [ -n "$SELECT_CONTAINER" ]; then
            [ -z "$container" -o "$container" == SS ] && container="$SELECT_CONTAINER"
        fi
        local command="$1"; shift
        [ -n "$command" ] || command=bash
        ${FAKE:+qvals} "$DOCKER" exec -it "$container" "$command" "$@"
    fi
}

function default_docker_deploy() {
    ${FAKE:+qvals} "$DOCKER" \
        stack deploy \
        "${replace_deploy_args[@]}" "${deploy_args[@]}" \
        "$container_name" "$@"
}
function docker_deploy() { default_docker_deploy "$@"; }
function auto_deploy() {
    local -a replace_env_args env_args
    local -a replace_deploy_args deploy_args
    local project_name container_name
    if [ -f docker-compose.yml -o -f docker-stack.yml ]; then
        docker_set_deploy_args set_container_name
        [ -n "$WITH_REGISTRY_AUTH" ] && replace_deploy_args+=(--with-registry-auth)
        docker_deploy "$@"
    else
        die "Impossible de trouver ni docker-compose.yml ni docker-stack.yml"
    fi
}

function default_docker_service() {
    ${FAKE:+qvals} "$DOCKER" service "$@"
}
function docker_service() { default_docker_service "$@"; }
function auto_service() {
    if [ -f docker-compose.yml -o -f docker-stack.yml ]; then
        local command="$1"; shift
        [ -n "$command" ] || {
            eerror "Vous devez spécifier la commande"
            return 1
        }
        local -a command_args
        [ "$command" == update -a -n "$WITH_REGISTRY_AUTH" ] && command_args+=(--with-registry-auth)
        case "$command" in
        ls) # ces commandes n'ont pas besoin du nom de service
            docker_service "$command" "$@"
            ;;
        *) # ces commandes ont besoin du nom du service
            local service="$1"; shift
            [ -n "$service" ] || {
                eerror "Vous devez spécifier le nom du service"
                return 1
            }
            local -a replace_env_args env_args
            local -a replace_deploy_args deploy_args
            local project_name container_name
            docker_set_deploy_args set_container_name
            service="${container_name}_${service#${container_name}_}"
            docker_service "$command" "${command_args[@]}" "$@" "$service"
            ;;
        esac
    else
        ${FAKE:+qvals} "$DOCKER" service "$@"
    fi
}

function __format() {
    # formatter une liste de valeur. chaque argument est de la forme [SIZE:]VALUE
    # il ne faudrait utiliser la forme VALUE que pour le dernier argument
    local size value
    for value in "$@"; do
        if [[ "$value" == *:* ]]; then
            size="${value%%:*}"
            value="${value#*:}"
        else
            size=
        fi
        if [ -n "$size" ]; then
            while [ "${#value}" -lt "$size" ]; do
                value="$value "
            done
            if [ "${#value}" -gt "$size" ]; then
                [ "$size" -gt 1 ] && value="${value:0:$(($size - 1))}"
                value="$value"$'\xe2\x80\xa6'
            fi
        fi
        echo -n "${value} "
    done
    echo
}

function __status_query_task() {
    setx taskData="$DOCKER" inspect "$taskID"
    setx taskID=json_get 0.ID <<<"$taskData"
    setx taskSlot=json_get 0.Slot <<<"$taskData"
    setx taskStatus=json_get 0.Status.Err <<<"$taskData"
    setx serviceID=json_get 0.ServiceID <<<"$taskData"
}
function __status_query_service() {
    [ "$serviceID" == "$pServiceID" ] && return
    setx serviceData="$DOCKER" inspect "$serviceID"
    setx serviceName=json_get 0.Spec.Name <<<"$serviceData"
    setx serviceUpdateStatus=json_get 0.UpdateStatus.Message <<<"$serviceData"
    pServiceID="$serviceID"
}
function __status_process_data() {
    __status_query_task
    __status_query_service
    if [ -n "$serviceUpdateStatus" ]; then
        echo "\
================
$serviceName: $serviceUpdateStatus
================"
        serviceUpdateStatus=
    fi
    if [ "$num" -eq 0 ]; then
        # afficher les en-têtes
        __format 3:num 48:taskName 32:node 8:dState 20:cState :error
    fi
    if [ -z "$fnum" -o "$num" == "$fnum" ]; then
        taskName="$serviceName.$taskSlot"
        __format 3:"$num" 48:"$taskName.$taskID" 32:"$node" 8:"$desiredState" 20:"$currentState" :"$taskStatus"
    fi
    if [ "$num" == "$fnum" ]; then
        "$DOCKER" service logs -f "$taskID"
    fi
    let num=num+1
}
function auto_status() {
    local -a services
    local service
    if [[ "$1" == *=* ]]; then
        : # le premier argument est un filtre
    elif [ -n "$1" -a -z "${1//[0-9]/}" ]; then
        : # le premier argument est un numéro d'instance
    else
        # le premier argument est un nom de service
        service="$1"; shift
    fi
    if [ -n "$service" ]; then
        if [ -f docker-compose.yml -o -f docker-stack.yml ]; then
            # si on est dans répertoire de projet, il est possible de spécifier
            # le service sans préfixe
            local -a replace_env_args env_args
            local -a replace_deploy_args deploy_args
            local project_name container_name
            docker_set_deploy_args set_container_name
            service="${container_name}_${service#${container_name}_}"
        fi
        services=("$service")
    else
        # lister les services qui tournent actuellement
        setx -a services=docker service ls --format "{{.Name}}"
    fi

    local -a psargs
    local filter fnum
    psargs=(
        --format "taskID={{.ID}};node={{.Node}};desiredState='{{.DesiredState}}';currentState='{{.CurrentState}}';__status_process_data"
    )
    while [[ "$1" == *=* ]]; do
        filter="$1"
        case "$filter" in
        =r) filter="desired-state=running";;
        =k) filter="desired-state=shutdown";;
        =a) filter="desired-state=accepted";;
        esac
        psargs+=(--filter "$filter")
        shift
    done
    fnum="$1"; shift

    local num=0 taskData taskStatus taskSlot pServiceID serviceID serviceData serviceName serviceUpdateStatus
    for service in "${services[@]}"; do
        eval "$("$DOCKER" service ps "$service" "${psargs[@]}")"
    done
}

function default_compose_show_ip() {
    local -a cmd cids; local cid
    cmd=(
        docker-compose
        "${replace_env_args[@]}" "${env_args[@]}"
        ps -q "${replace_show_ip_args[@]}" "${show_ip_args[@]}"
        "$@"
    )
    if [ -n "$FAKE" ]; then
        echo "docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \"\$($(qvals "${cmd[@]}"))\""
    else
        setx -a cids="${cmd[@]}" 2>/dev/null
        for cid in "${cids[@]}"; do
            "$DOCKER" inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$cid"
        done
    fi
}
function default_docker_show_ip() {
    ${FAKE:+qvals} "$DOCKER" inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
           "${replace_show_ip_args[@]}" "${show_ip_args[@]}" \
           "$container_name" "$@"
}
function compose_show_ip() { default_compose_show_ip "$@"; }
function docker_show_ip() { default_docker_show_ip "$@"; }
function auto_show_ip() {
    local -a replace_env_args env_args
    local -a replace_show_ip_args show_ip_args
    local project_name container_name
    if [ -f docker-compose.yml ]; then
        compose_set_env_args
        compose_show_ip "$@"
    else
        docker_set_env_args
        docker_check_name set_container_name
        docker_show_ip "$@"
    fi
}

function default_compose_systemd_unit() {
    local docker_compose="$(which docker-compose 2>/dev/null)"
    if [ -z "$docker_compose" ]; then
        if [ -x /usr/bin/docker-compose ]; then
            docker_compose=/usr/bin/docker-compose
        elif [ -x /usr/local/bin/docker-compose ]; then
            docker_compose=/usr/local/bin/docker-compose
        else
            die "Impossible de trouver docker-compose"
        fi
    fi
    setx startcmd=qvals "$docker_compose" \
        "${replace_env_args[@]}" "${env_args[@]}" \
        up "${replace_run_args[@]}" "${run_args[@]}" \
        "${replace_user_args[@]}" "${user_args[@]}" "$@"
    setx stopcmd=qvals "$docker_compose" down
}
function default_docker_systemd_unit() {
    local docker="$(which "$DOCKER" 2>/dev/null)"
    if [ -z "$docker" ]; then
        if [ -x /usr/bin/docker ]; then
            docker=/usr/bin/docker
        elif [ -x /usr/local/bin/docker ]; then
            docker=/usr/local/bin/docker
        else
            die "Impossible de trouver docker"
        fi
    fi
    setx startcmd=qvals "$docker" run \
           "${replace_env_args[@]}" "${env_args[@]}" \
           "${replace_run_args[@]}" "${run_args[@]}" \
           "$NAME" \
           "${replace_user_args[@]}" "${user_args[@]}" "$@"
    setx stopcmd=qvals "$docker" stop "$container_name"
}
function compose_systemd_unit() { default_compose_systemd_unit "$@"; }
function docker_systemd_unit() { default_docker_systemd_unit "$@"; }
function auto_systemd_unit() {
    local -a replace_env_args env_args
    local -a replace_run_args run_args
    local -a replace_user_args user_args
    local project_name container_name startcmd stopcmd
    local tmpscript; ac_set_tmpfile tmpscript

    estep "Génération de l'unité systemd"
    export COMPOSE_PROJECT_NAME=
    if [ -f docker-compose.yml ]; then
        compose_set_env_args set_container_name
        replace_run_args=(-d --no-color)
        compose_systemd_unit "$@"
        if [ -z "$HOST" -a -f .env ]; then
            source ./.env
            if [ -n "$PROFILE" ]; then
                HOST="${PROFILE^^}_HOST"; HOST="${!HOST}"
            fi
        fi
    else
        docker_set_env_args
        docker_check_name set_container_name
        replace_run_args=(-d --name "$container_name")
        docker_systemd_unit "$@"
    fi
    [ -n "$COMPOSE_PROJECT_NAME" ] || COMPOSE_PROJECT_NAME="$project_name"
    chmod 755 "$tmpscript"
    cat >"$tmpscript" <<EOF
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
cat >/etc/systemd/system/$container_name.service <<EOD
[Unit]
Description=$project_name stack ($PROFILE)
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=$(pwd)
Environment=$(qval "COMPOSE_PROJECT_NAME=$COMPOSE_PROJECT_NAME")
ExecStart=$startcmd
ExecStop=$stopcmd
TimeoutStopSec=300

[Install]
WantedBy=multi-user.target
EOD
systemctl daemon-reload
systemctl enable $container_name.service
EOF

    estep "Installation de l'unité systemd"
    local_run "$HOST" "$tmpscript"
}

function auto_copy() {
    local src="$1"; shift
    local dest="$1"; shift
    if [ -n "$SELECT_CONTAINER" ]; then
        if [ "${src#SS:}" != "$src" ]; then
            src="$SELECT_CONTAINER${src#SS}"
        elif [ "${src#:}" != "$src" ]; then
            src="$SELECT_CONTAINER$src"
        fi
        if [ "${dest#SS:}" != "$dest" ]; then
            dest="$SELECT_CONTAINER${dest#SS}"
        elif [ "${dest#:}" != "$dest" ]; then
            dest="$SELECT_CONTAINER$dest"
        fi
    fi

    "$DOCKER" cp "$src" "$dest" "$@"
}

function default_local_composer() {
    # lancement direct
    case "$1" in
    rootshell|rshell|rootbash|rbash)
        shift
        # ewarn parce qu'on est pas root dans ce shell contrairement à ce qui est demandé
        ewarn "Lancement d'un shell utilisateur alors qu'un shell root est demandé"
        bash "$@"
        ;;
    usershell|shell|userbash|bash)
        shift
        estep "Lancement d'un shell utilisateur"
        bash "$@"
        ;;
    *)
        if [ -n "$COMPOSER_CMD" ]; then :
        elif [ -x composer.phar ]; then COMPOSER_CMD=./composer.phar
        elif [ -x /usr/bin/composer ]; then COMPOSER_CMD=/usr/bin/composer
        else
            eerror "Impossible de trouver composer"
            return 1
        fi
        "$COMPOSER_CMD" "$@"
        ;;
    esac
}
function default_docker_composer() {
    # lancement dans un container
    local user group projdir actualcmd args
    setx user=id -un; setx user=getent passwd "$user"
    setx group=id -gn; setx group=getent group "$group"
    setx projdir=pwd
    case "$1" in
    rootshell|rshell|rootbash|rbash)
        shift
        actualcmd='eval "bash $args"'
        ;;
    usershell|shell|userbash|bash)
        shift
        actualcmd='eval "su-exec \"$user\" bash $args"'
        ;;
    *)
        actualcmd='eval "su-exec \"$user\" \"$composer\" $args"'
        ;;
    esac
    setx args=qvals "$@"

    local -a basecmd cmd setupscript runscript
    basecmd=(
        -e user="$user"
        -e group="$group"
        -e projdir="$projdir"
        -e setup="$COMPOSER_SETUP"
        -e composer="$COMPOSER_CMD"
        -e args="$args"
        -v "$HOME:$HOME"
    )
    if [ "${projdir#$HOME/}" == "$projdir" ]; then
        # si le répertoire de projet ne se trouve pas dans $HOME, le monter aussi
        cmd+=(-v "$projdir:$projdir")
    fi
    setupscript='eval "$setup"'
    runscript='
echo "$user" >>/etc/passwd; user="${user%%:*}"
echo "$group" >>/etc/group; group="${group%%:*}"

cd "$projdir"
if [ -n "$composer" ]; then :
elif [ -x composer.phar ]; then composer=./composer.phar
elif [ -x /usr/bin/composer ]; then composer=/usr/bin/composer
else
    echo "ERROR: Impossible de trouver composer"
    exit 1
fi
'"$actualcmd"

    if [ -n "$COMPOSER_SETUP" ]; then
        # lancement dans un container docker à préparer
        local NAME project_name container_name dkid
        if [ -f docker-compose.yml ]; then
            compose_set_project_name set_container_name
        else
            NAME="$(basename -- "$(pwd)")"
            docker_check_name set_container_name
        fi
        container_name="dk_composer_${container_name}"

        # vérifier l'existence de l'image
        setx dkid=docker image ls --format '{{.ID}}' "${container_name}_image"

        # créer le container le cas échéant
        if [ -z "$dkid" ]; then
            estep "Création du container $container_name avec l'image $COMPOSER_IMAGE"
            cmd=(
                "$DOCKER" create -it --name "${container_name}_ct"
                "${basecmd[@]}"
                "$COMPOSER_IMAGE"
                bash -c "$setupscript"
            )
            setx dkid="${cmd[@]}" || return 1
            "$DOCKER" container start -ai "$dkid" || return 1
            "$DOCKER" container commit "$dkid" "${container_name}_image" || return 1
            "$DOCKER" container rm "$dkid" || return 1
        fi

        # prendre comme image le container créé
        COMPOSER_IMAGE="${container_name}_image"
    fi

    cmd=(
        "$DOCKER" run -it --rm
        "${basecmd[@]}"
        "$COMPOSER_IMAGE"
        bash -c "$runscript"
    )
    "${cmd[@]}"
}
function local_composer() { default_local_composer "$@"; }
function docker_composer() { default_docker_composer "$@"; }
function auto_composer() {
    local COMPOSER_PHP=
    local COMPOSER_PHP_MAX=
    local COMPOSER_IMAGE="$DEFAULT_COMPOSER_IMAGE"
    local COMPOSER_MACHINE=-u
    local COMPOSER_CMD=
    local COMPOSER_SETUP=
    [ -f .composer.conf ] && source ./.composer.conf
    # les premiers arguments peuvent service à redéfinir les variables
    while [ $# -gt 0 ]; do
        case "$1" in
        COMPOSER_PHP=*) setv "$1"; shift;;
        COMPOSER_PHP_MAX=*) setv "$1"; shift;;
        COMPOSER_IMAGE=*) setv "$1"; shift;;
        COMPOSER_MACHINE=*) setv "$1"; shift;;
        COMPOSER_CMD=*) setv "$1"; shift;;
        COMPOSER_SETUP=*) setv "$1"; shift;;
        *) break;;
        esac
    done

    local use_image
    if [ -n "$COMPOSER_PHP_MAX" -a "$COMPOSER_PHP_MAX" != none ]; then
        # Vérifier la version de PHP
        php -r '
$version = $argv[1];
if (strpos($version, ".") !== false) {
  $version = explode(".", $version);
  $version = $version[0] * 10000 + $version[1] * 100 + (isset($version[2])? $version[2]: 0);
}
exit((PHP_VERSION_ID > $version)? 0: 1);
' -- "$COMPOSER_PHP_MAX" && use_image=1
    fi
    if [ -n "$use_image" ]; then
        : # ok, on a déjà décidé qu'il faut utiliser une image
    elif [ -z "$COMPOSER_PHP" ]; then
        # pas de version minimum, tester simplement la valeur de COMPOSER_IMAGE
        [ "$COMPOSER_IMAGE" != none ] && use_image=1
    elif [ "$COMPOSER_PHP" == force -o "$COMPOSER_PHP" == any ]; then
        use_image=1
    else
        # Vérifier la version de PHP
        php -r '
$version = $argv[1];
if (strpos($version, ".") !== false) {
  $version = explode(".", $version);
  $version = $version[0] * 10000 + $version[1] * 100 + (isset($version[2])? $version[2]: 0);
}
exit((PHP_VERSION_ID < $version)? 0: 1);
' -- "$COMPOSER_PHP" && use_image=1
    fi

    if [ -n "$use_image" ]; then
        [ "$COMPOSER_IMAGE" != none ] || die "Vous devez spécifier l'image à utiliser pour composer"

        local PREVIOUS_DOCKER_MACHINE_NAME="$DOCKER_MACHINE_NAME"
        if [ -n "$COMPOSER_MACHINE" -a "$DOCKER_MACHINE_NAME" != "$COMPOSER_MACHINE" ]; then
            local -x DOCKER_TLS_VERIFY= DOCKER_HOST= DOCKER_CERT_PATH= DOCKER_MACHINE_NAME=
            if [ "$COMPOSER_MACHINE" != -u ]; then
                local env
                setx env=docker-machine env "$COMPOSER_MACHINE" 2>/dev/null || {
                    eerror "$COMPOSER_MACHINE: une erreur s'est produite lors de la sélection du noeud avec docker-machine"
                    return 1
                }
                eval "$env"
            fi
        fi

        docker_composer "$@"
    else
        local_composer "$@"
    fi
}

################################################################################

function resolve_dm_alias() {
    local alias_dm alias dm
    # chercher d'abord dans les définitions par défaut
    for alias_dm in "${DM_ALIASES[@]}"; do
        alias="${alias_dm%%:*}"
        dm="${alias_dm#*:}"
        if [ "$1" == "$alias" ]; then
            echo "$dm"
            return 0
        fi
    done
    # puis chercher dans les répertoires de cluster
    local clusterdir DM_ALIASES
    for clusterdir in "${CLUSTERDIRS[@]}"; do
        DM_ALIASES=()
        [ -f "$clusterdir/0config/configure.conf" ] || continue
        source "$clusterdir/0config/configure.conf"
        for alias_dm in "${DM_ALIASES[@]}"; do
            alias="${alias_dm%%:*}"
            dm="${alias_dm#*:}"
            if [ "$1" == "$alias" ]; then
                echo "$dm"
                return 0
            fi
        done
    done
    # sinon, laisser en l'état
    echo "$1"
    return 1
}

function resolve_dm_profile() {
    local dm_profile dm profile
    # chercher d'abord dans les définitions par défaut
    for dm_profile in "${DM_PROFILES[@]}"; do
        dm="${dm_profile%%:*}"
        profile="${dm_profile#*:}"
        if [ "$1" == "$dm" ]; then
            echo "$profile"
            return 0
        fi
    done
    # puis chercher dans les répertoires de cluster
    local clusterdir DM_PROFILES
    for clusterdir in "${CLUSTERDIRS[@]}"; do
        DM_PROFILES=()
        [ -f "$clusterdir/0config/configure.conf" ] || continue
        source "$clusterdir/0config/configure.conf"
        for dm_profile in "${DM_PROFILES[@]}"; do
            dm="${dm_profile%%:*}"
            profile="${dm_profile#*:}"
            if [ "$1" == "$dm" ]; then
                echo "$profile"
                return 0
            fi
        done
    done
    # sinon, afficher le profil par défaut
    echo "$DEFAULT_PROFILE"
    return 1
}

################################################################################

# support limité docker / podman
if progexists docker; then DOCKER=docker
elif progexists podman; then DOCKER=podman
else DOCKER=docker
fi

DEFAULT_PROFILE=devel
# pour le moment ne pas lancer composer dans un container par défaut
DEFAULT_COMPOSER_IMAGE=none #docker.univ-reunion.fr/image/apache-php-myiccas-utils:d10
PROFILE=
DM_ALIASES=()
DM_PROFILES=()
CLUSTERDIRS=()
set_defaults dk
export PROFILE

chdir=
CONFIG=
DM_SET_MACHINE=
SELECT_CONTAINER=
SELECT_MACHINE=
USE_STACK=
FAKE=
VARS=()
FORCE=
NO_CACHE=
HOST=
WITH_REGISTRY_AUTH=1
update_apps_mode=ub
update_apps_devel=
update_apps_origin=
update_apps_branch=
args=(
    --help '$exit_with display_help'
    -d:,--chdir: chdir=
    -c:,--config: CONFIG=
    -p:,--profile: PROFILE=
    -P,--prod PROFILE=prod
    -T,--test PROFILE=test
    -m:,--set-machine: DM_SET_MACHINE=
    -s:,--select-service: SELECT_CONTAINER=
    -t:,--select-machine: SELECT_MACHINE=
    --stack USE_STACK=1
    -n,--fake FAKE=1
    -e:,--build-arg:,--env: VARS
    -f,--force FORCE=1
    -j,--no-cache NO_CACHE=1
    -h:,--host: HOST=
    -g,--ug,--no-update-apps update_apps_mode=b
    -u,--uu,--update-apps-only update_apps_mode=u
    -w,--uw,--update-apps-devel update_apps_devel=1
    --uo:,--update-apps-origin: update_apps_origin=
    --ub:,--update-apps-branch: update_apps_branch=
    -l,--without-registry-auth WITH_REGISTRY_AUTH=
)
parse_args "$@"; set -- "${args[@]}"

progexists docker-machine && DM_AVAILABLE=1 || DM_AVAILABLE=
if [ -n "$DM_SET_MACHINE" ]; then
    [ -n "$DM_AVAILABLE" ] || die "docker-machine n'est pas disponible"
    setx DM_SET_MACHINE=resolve_dm_alias "$DM_SET_MACHINE"
    [ "$DM_SET_MACHINE" == - ] && DM_SET_MACHINE=-u
    setx dm_env=docker-machine env "$DM_SET_MACHINE" || die
    eval "$dm_env"
    # pour warning ci-dessous
    [ "$DM_SET_MACHINE" == -u ] && DM_SET_MACHINE=
fi

if [ -n "$SELECT_MACHINE" -o -n "$SELECT_CONTAINER" ]; then
    function __ss_process_data() {
        [ -n "$found" ] && return
        __status_query_task
        __status_query_service
        if [ -n "$slot" ]; then
            if [ "$taskSlot" == "$slot" ]; then
                # ne prendre que le slot qui correspond
                found=1
            fi
        else
            # prendre le premier
            found=1
        fi
        taskName="$serviceName.$taskSlot"
    }

    if [ -n "$SELECT_MACHINE" ]; then service="$SELECT_MACHINE"
    elif [ -n "$SELECT_CONTAINER" ]; then service="$SELECT_CONTAINER"
    fi
    if [ -f docker-compose.yml -o -f docker-stack.yml ]; then
        # si on est dans répertoire de projet, il est possible de spécifier
        # le service sans préfixe
        docker_set_deploy_args set_container_name
        service="${container_name}_${service#${container_name}_}"
    fi
    if [[ "$service" == *.* ]]; then
        slot="${service##*.}"
        service="${service%.*}"
    else
        slot=
    fi

    psargs=(
        --filter desired-state=running
        --format "taskID={{.ID}};node={{.Node}};__ss_process_data"
    )
    found=
    eval "$("$DOCKER" service ps "$service" "${psargs[@]}" 2>/dev/null)"

    if [ -n "$found" ]; then
        SELECT_CONTAINER="$serviceName.$taskSlot.$taskID"
        if [ -n "$SELECT_MACHINE" ]; then
            DM_SET_MACHINE="$node"
        else
            enote "Sélection du container $SELECT_CONTAINER"
        fi
    else
        die "$service${slot:+.$slot}: service introuvable"
    fi
fi
if [ -n "$SELECT_MACHINE" ]; then
    enote "Sélection de la machine $DM_SET_MACHINE"
    [ -n "$SELECT_CONTAINER" ] && enote "Sélection du container $SELECT_CONTAINER"

    [ -n "$DM_AVAILABLE" ] || die "docker-machine n'est pas disponible"
    setx dm_env=docker-machine env "$DM_SET_MACHINE" || die
    eval "$dm_env"
fi

if [ -n "$DM_AVAILABLE" ]; then
    setx DEFAULT_PROFILE=resolve_dm_profile "$DOCKER_MACHINE_NAME" && found=1 || found=
    if [ -n "$DM_SET_MACHINE" -a -z "$PROFILE" -a -z "$found" ]; then
        ewarn "\
Aucun profil n'a été défini pour $DM_SET_MACHINE dans ~/etc/default/dk
== Le profil $DEFAULT_PROFILE sera utilisé =="
        sleep 3
    fi
fi

# construire par défaut
[ $# -eq 0 ] && set -- build
[ -n "$PROFILE" ] || PROFILE="$DEFAULT_PROFILE"

[ -n "$chdir" ] && { cd "$chdir" || die; }
update_apps_mode="${update_apps_mode}${update_apps_devel:+w}"

[ -f .dk.scripts.sh ] && source ./.dk.scripts.sh
[ -f dk.scripts.sh ] && source ./dk.scripts.sh

while [ $# -gt 0 ]; do
    [ "$1" == -- ] && { shift; continue; }
    cmd="$1"; shift
    case "$cmd" in
    get-profile|get_profile|profile)
        echo "$PROFILE"
        exit 0
        ;;
    b|build)
        build_set_options "$update_apps_mode" "$update_apps_origin" "$update_apps_branch"
        [ -f .build.scripts.sh ] && source ./.build.scripts.sh
        [ -f build.scripts.sh ] && source ./build.scripts.sh
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        auto_build "${args[@]}" || die
        ;;
    p|push)
        [ -f .build.scripts.sh ] && source ./.build.scripts.sh
        [ -f build.scripts.sh ] && source ./build.scripts.sh
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        auto_push "${args[@]}" || die
        ;;
    s|start)
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        auto_up "${args[@]}" || die
        ;;
    k|stop)
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        auto_stop "${args[@]}" || die
        ;;
    1|up)
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        auto_up "${args[@]}" && auto_logs || die
        ;;
    l|logs)
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        auto_logs "${args[@]}" || die
        ;;
    0|down)
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        auto_down "${args[@]}" || die
        ;;
    r|run)
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        auto_run "${args[@]}" || die
        ;;
    x|exec)
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        auto_exec "${args[@]}" || die
        ;;
    d|brd)
        do_auto_down=1
        function auto_down_trap() {
            [ -n "$do_auto_down" ] && auto_down
        }
        trap auto_down_trap 1 3 15 EXIT

        build_set_options "$update_apps_mode" "$update_apps_origin" "$update_apps_branch"
        [ -f .build.scripts.sh ] && source ./.build.scripts.sh
        [ -f build.scripts.sh ] && source ./build.scripts.sh
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        __parse_bargs "${args[@]}"
        if auto_build "${bargs[@]}"; then
            auto_up "${args[@]}" && auto_logs || die
        else
            do_auto_down=
        fi
        ;;
    bs)
        build_set_options "$update_apps_mode" "$update_apps_origin" "$update_apps_branch"
        [ -f .build.scripts.sh ] && source ./.build.scripts.sh
        [ -f build.scripts.sh ] && source ./build.scripts.sh
        USE_STACK=1
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        __parse_bargs "${args[@]}"
        auto_build "${bargs[@]}" && auto_up "${args[@]}" || die
        ;;
    br)
        build_set_options "$update_apps_mode" "$update_apps_origin" "$update_apps_branch"
        [ -f .build.scripts.sh ] && source ./.build.scripts.sh
        [ -f build.scripts.sh ] && source ./build.scripts.sh
        USE_STACK=1
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        __parse_bargs "${args[@]}"
        auto_build "${bargs[@]}" && auto_run "${args[@]}" || die
        ;;
    y|deploy)
        USE_STACK=1
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        auto_deploy "${args[@]}" || die
        ;;
    by|bd)
        build_set_options "$update_apps_mode" "$update_apps_origin" "$update_apps_branch"
        [ -f .build.scripts.sh ] && source ./.build.scripts.sh
        [ -f build.scripts.sh ] && source ./build.scripts.sh
        USE_STACK=1
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        __parse_bargs "${args[@]}"
        auto_build "${bargs[@]}" && auto_deploy "${args[@]}" || die
        ;;
    bp)
        build_set_options "$update_apps_mode" "$update_apps_origin" "$update_apps_branch"
        [ -f .build.scripts.sh ] && source ./.build.scripts.sh
        [ -f build.scripts.sh ] && source ./build.scripts.sh
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        __parse_bargs "${args[@]}"
        auto_build "${bargs[@]}" && auto_push "${args[@]}" || die
        ;;
    bpy|bpd)
        build_set_options "$update_apps_mode" "$update_apps_origin" "$update_apps_branch"
        [ -f .build.scripts.sh ] && source ./.build.scripts.sh
        [ -f build.scripts.sh ] && source ./build.scripts.sh
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        __parse_bargs "${args[@]}"
        auto_build "${bargs[@]}" && auto_push && auto_deploy "${args[@]}" || die
        ;;
    ser|service)
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        auto_service "${args[@]}" || die
        ;;
    sta|status)
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        auto_status "${args[@]}" || die
        ;;
    u|update)
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        auto_service update "${args[@]}" || die
        ;;
    scale)
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        auto_service scale "${args[@]}" || die
        ;;
    ip|show-ip)
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        auto_show_ip "${args[@]}" || die
        ;;
    systemd-unit|systemd)
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        auto_systemd_unit "${args[@]}" || die
        ;;
    cp|copy)
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        enote "Profil $PROFILE"
        auto_copy "${args[@]}" || die
        ;;
    ps)
        pscmd=(
            "$DOCKER" container ps -a
            --format "table {{.ID}}\t{{.Image}}\t{{.Names}}\t{{.Status}}\t{{.RunningFor}}\t{{.Ports}}"
        )
        filtercmd=(cat)
        awkscript='
NR == 1 { print; next }
($2 ~ filter || $3 ~ filter) { print }
'
        if [ $# -eq 0 ]; then
            # pas de filtre
            :
        elif [ "$1" == -- ]; then
            # fin de la commande
            shift
        elif [[ "$1" == *=* ]]; then
            # filtre docker
            pscmd+=(--filter "$1")
            shift
        else
            # expression régulière
            filtercmd=(awk -v filter="$1" "$awkscript")
            shift
        fi
        set -o pipefail
        "${pscmd[@]}" | "${filtercmd[@]}" || die
        ;;
    ls)
        lscmd=(docker image ls)
        filtercmd=(cat)
        awkscript='
BEGIN {
  if (split(filter, parts, /:/) > 1) {
    filter = parts[1]
    tag = parts[2]
  } else {
    tag = ""
  }
}
NR == 1 { print; next }
(filter == "" || $1 ~ filter) && (tag == "" || $2 ~ tag) { print }
'
        if [ $# -eq 0 ]; then
            # pas de filtre
            :
        elif [ "$1" == -- ]; then
            # fin de la commande
            shift
        elif [[ "$1" == *=* ]]; then
            # filtre docker
            lscmd+=(--filter "$1")
            shift
        else
            # expression régulière
            filtercmd=(awk -v filter="$1" "$awkscript")
            shift
        fi
        set -o pipefail
        "${lscmd[@]}" | "${filtercmd[@]}" || die
        ;;
    pull)
        lscmd=(docker image ls)
        filtercmd=(awk 'NR == 1 { print; next } $2 !~ /</ { print }')
        awkscript='
BEGIN {
  if (split(filter, parts, /:/) > 1) {
    filter = parts[1]
    tag = parts[2]
  } else {
    tag = ""
  }
}
NR == 1 { print; next }
(filter == "" || $1 ~ filter) && (tag == "" || $2 ~ tag) && $2 !~ /</ { print }
'
        if [ $# -eq 0 -o "$1" == -- ]; then
            # pas de filtre
            ewarn "pull: Vous devez spécifier l'image à mettre à jour"
            continue
        elif [[ "$1" == *=* ]]; then
            # filtre docker
            lscmd+=(--filter "$1")
            shift
        else
            # expression régulière
            filtercmd=(awk -v filter="$1" "$awkscript")
            shift
        fi
        all="$("${lscmd[@]}" | "${filtercmd[@]}")"
        setx -a images awk 'NR == 1 { next } { print $1 ":" $2 }' <<<"$all"
        if [ ${#images[*]} -gt 0 ]; then
            echo "$all"
            ask_yesno "Etes-vous sûr de vouloir mettre à jour ces images?" O || die
            for image in "${images[@]}"; do
                "$DOCKER" pull "$image" || die
            done
        fi
        ;;
    rm)
        lscmd=(docker image ls)
        filtercmd=(cat)
        awkscript='
BEGIN {
  if (split(filter, parts, /:/) > 1) {
    filter = parts[1]
    tag = parts[2]
  } else {
    tag = ""
  }
}
NR == 1 { print; next }
(filter == "" || $1 ~ filter) && (tag == "" || $2 ~ tag) { print }
'
        if [ $# -eq 0 -o "$1" == -- ]; then
            # pas de filtre
            ewarn "rm: Vous devez spécifier l'image à supprimer"
            continue
        elif [[ "$1" == *=* ]]; then
            # filtre docker
            lscmd+=(--filter "$1")
            shift
        else
            # expression régulière
            filtercmd=(awk -v filter="$1" "$awkscript")
            shift
        fi
        all="$("${lscmd[@]}" | "${filtercmd[@]}")"
        setx -a images awk 'NR == 1 { next } { if ($1 == "<none>" || $2 == "<none>") { print $3 } else { print $1 ":" $2 } }' <<<"$all"
        if [ ${#images[*]} -gt 0 ]; then
            echo "$all"
            ask_yesno "Etes-vous sûr de vouloir supprimer ces images?" || die
            "$DOCKER" image rm ${FORCE:+--force} "${images[@]}" || die
        fi
        ;;
    X|prune)
        "$DOCKER" container prune -f || die
        "$DOCKER" image prune -f || die
        ;;
    composer|c|ci|cu|cr|cs)
        build_set_options "$update_apps_mode" "$update_apps_origin" "$update_apps_branch"
        [ -f .build.scripts.sh ] && source ./.build.scripts.sh
        [ -f build.scripts.sh ] && source ./build.scripts.sh
        args=()
        if [ "$cmd" == ci ]; then args+=(install)
        elif [ "$cmd" == cu ]; then args+=(update)
        elif [ "$cmd" == cr ]; then args+=(rshell)
        elif [ "$cmd" == cs ]; then args+=(shell)
        fi
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        auto_composer "${args[@]}"
        ;;
    _*|*_)
        # transmettre directement à docker
        cmd="${cmd#_}"
        cmd="${cmd%_}"
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        "$DOCKER" "$cmd" "${args[@]}"
        ;;
    *)
        # transmettre directement à docker
        args=()
        while [ $# -gt 0 -a "$1" != -- ]; do
            args+=("$1"); shift
        done
        "$DOCKER" "$cmd" "${args[@]}"
    esac
done