#!/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émantique. COMMANDES get-profile Afficher le profil courant b|build [NAME=VALUE...] [SERVICE] Construire les images. Les variables NAME=VALUE sont des 'build args' Avec les projets de type docker-compose, le fichier .env est honoré normalement. Si les fichiers .shared_env et/ou .MACHINE_env existent, où MACHINE est la machine actuellement sélectionnée, alors les merger pour créer le fichier .env Par défaut, MACHINE vaut \$DOCKER_MACHINE_NAME. Si cette variable n'est pas définie, prendre \$HOSTNAME 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= a task's ID ou prefix - name= a task's name or prefix - node= 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=([:tag]|| ) containers created from an image or a descendant. - before=(|) - expose=([/]|/[]) - exited= an exit code of - health=(starting|healthy|unhealthy|none) - id= a container's ID - is-task=(true|false) - label= or label== - name= a container's name - network=(|) - publish=([/]|/[]) - since=(|) - status=(created|restarting|removing|running|paused|exited) - volume=(|) 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= or label== - before=([:tag]|| ) - since=([:tag]|| ) - 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 aliases 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 Spécifier 'none' ou 'system' pour lancer directement composer sans passer par une 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 maven|mvn|mvr|mvs|java [args...] Frontend pour lancer maven ou Java à l'intérieur d'un container. Les commandes 'mvr', 'mvs' et 'java' sont des aliases pour 'maven rshell', 'maven shell' et 'maven java' respectivement S'il existe un fichier .maven.conf dans le répertoire du projet, il est sourcé. Ce fichier définit des variables qui indiquent comment la commande mvn est lancée. Les variables suivantes peuvent être définies: * MAVEN_JAVA -- Version de java à sélectionner à l'intérieur de l'image. Spécifier 'any' ou 'force' pour prendre la valeur par défaut. Spécifier 'none' ou 'system' pour ne pas utiliser l'image * MAVEN_IMAGE -- Image utilisée pour lancer mvn. La valeur par défaut est $DEFAULT_MAVEN_IMAGE Spécifier 'none' pour lancer directement mvn sans passer par une image docker, même si MAVEN_JAVA est renseigné. 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 * MAVEN_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. * MAVEN_CMD -- Chemin vers l'exécutable mvn. Par défaut, utiliser la commande trouvée dans le PATH * MAVEN_SETUP -- Liste de commandes à lancer pour configurer le container. Dans ce cas, un container ayant pour base \$MAVEN_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 mvn, 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. La commande 'java' est une extension qui lance directement java au lieu de lancer la commande mvn. Il est possible de spécifier la version de java à sélectionner dans l'image e.g 'java7', 'java8', 'java11' Pour faciliter l'utilisation dans un script, les premiers arguments peuvent être utilisés pour redéfinir les variables MAVEN_*, e.g $scriptname maven MAVEN_IMAGE=none clean package 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, by, bpy) --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, by, bpy et update impliquent --stack -j, --no-cache Ne pas utiliser le cache lors du build -U, --pull Essayer de récupérer une version plus récente de l'image source -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: ## valeurs par défaut DEFAULT_DEVEL_SRCDIR= # répertoire de base des dépôts DEFAULT_BRANCH= # branche par défaut pour les checkouts DEFAULT_ORIGIN= # origine par défaut des branches CLEAN= # fonction de nettoyage APPS=() # applications à mettre à jour HOST_MAPPINGS=() # mappings d'hôtes pour les builds docker ## valeurs par défaut pour les projets composer COMPOSER_DEVEL_SRCDIR= # répertoire de base des projets composer COMPOSER_ACTION= # action projet composer (install|update|none) COMPOSER_ARGS=() # arguments de composer install|update COMPOSER_PHP= COMPOSER_PHP_MAX= COMPOSER_IMAGE= COMPOSER_CMD= COMPOSER_SETUP= ## valeurs par défaut pour les projets maven MAVEN_DEVEL_SRCDIR= # répertoire de base des projets maven MAVEN_ACTION= # action projet maven (package|none) MAVEN_ARGS=() # arguments de mvn MAVEN_JAVA= MAVEN_IMAGE= MAVEN_CMD= MAVEN_SETUP= ## pour chaque application définie dans APPS {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}_BRANCH= # branche à sélectionner pour le checkout {app}_ORIGIN= # origine de la branche à sélectionner {app}_TYPE= # type de projet (composer|none) {app}_BEFORE_BUILD=() # liste de commandes à lancer après le # checkout et avant le build du projet (pour # les types de projets supportés) {app}_AFTER_UPDATE=() # liste de commandes à lancer après le build # valeurs pour les projets composer. ces valeurs remplacent le cas # échéant celles définies dans le fichier .composer.conf du projet {app}_COMPOSER_ACTION= {app}_COMPOSER_ARGS=() {app}_COMPOSER_PHP= {app}_COMPOSER_PHP_MAX= {app}_COMPOSER_IMAGE= {app}_COMPOSER_CMD= {app}_COMPOSER_SETUP= {app}_MAVEN_ACTION= {app}_MAVEN_ARGS=() {app}_MAVEN_JAVA= {app}_MAVEN_IMAGE= {app}_MAVEN_CMD= {app}_MAVEN_SETUP= Certaines valeurs peuvent être valuées selon le profil {profile}_CLEAN= {profile}_APPS=() {profile}_HOST_MAPPINGS=() {profile}_COMPOSER_ACTION= {profile}_COMPOSER_ARGS=() {profile}_COMPOSER_PHP= {profile}_COMPOSER_PHP_MAX= {profile}_COMPOSER_IMAGE= {profile}_COMPOSER_CMD= {profile}_COMPOSER_SETUP= {app}_{profile}_ORIGIN= {app}_{profile}_BRANCH= {app}_{profile}_COMPOSER_ACTION= {app}_{profile}_COMPOSER_ARGS=() {app}_{profile}_COMPOSER_PHP= {app}_{profile}_COMPOSER_PHP_MAX= {app}_{profile}_COMPOSER_IMAGE= {app}_{profile}_COMPOSER_CMD= {app}_{profile}_COMPOSER_SETUP= {app}_{profile}_MAVEN_ACTION= {app}_{profile}_MAVEN_ARGS=() {app}_{profile}_MAVEN_JAVA= {app}_{profile}_MAVEN_IMAGE= {app}_{profile}_MAVEN_CMD= {app}_{profile}_MAVEN_SETUP= -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, \$COMPOSER_DEVEL_SRCDIR ou \$MAVEN_DEVEL_SRCDIR en fonction du type de projet --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: si SRC est définit, 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, COMPOSER_DEVEL_SRCDIR et MAVEN_DEVEL_SRCDIR sont les répertoires de base par défaut des projets ORIGIN vaut 'origin' par défaut BRANCH vaut par défaut 'master' dans le profil prod, 'develop' sinon Pour toutes les variables de type BRANCH, utiliser la syntaxe ^COMMIT pour ignorer ORIGIN et sélectionner un commit en particulier TYPE la valeur par défaut dépend des fichiers présents à la racine du projet - si un fichier composer.json existe, vaut 'composer' par défaut - si un fichier pom.xml existe, vaut 'maven' par défaut - sinon vaut 'none' par défaut BEFORE_BUILD Cette variable est une liste de commandes à lancer après le clonage (ou la mise à jour) du dépôt et avant le build, en fonction du type de projet. La syntaxe à utiliser est la même que pour AFTER_UPDATE AFTER_UPDATE Cette variable est une liste de commandes à lancer après la maj du dépôt et le build éventuel du projet - 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 BEFORE_BUILD et avant les commandes de AFTER_UPDATE. Les directives supportées sont 'install', 'update' et 'none' COMPOSER_ARGS options à utiliser avec 'composer \$COMPOSER_ACTION'. La valeur par défaut dépend du profil: prod: --no-dev -o test: --no-dev -o autres: (pas d'options) MAVEN_ACTION vaut 'package' par défaut. Indique ce qu'il faut faire pour un projet de type 'maven' après avoir lancé les commandes de BEFORE_BUILD et avant les commandes de AFTER_UPDATE. Les directives supportées sont: - 'install' (alias de 'clean package install') - 'package' (alias de 'clean package') - 'package_only' qui ne lance pas de clean avant le build - 'none' qui ne fait rien MAVEN_ARGS options à utiliser avec la commande 'mvn \$MAVEN_ACTION' 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)" # DEPRECATED [ -f build.env ] && eval "$(docker_parse_build_args build.env)" [ -f build.env.local ] && eval "$(docker_parse_build_args build.env.local)" [ -n "$PROFILE" -a -f ".build.$PROFILE.env" ] && eval "$(docker_parse_build_args ".build.$PROFILE.env")" # DEPRECATED [ -n "$PROFILE" -a -f "build.$PROFILE.env" ] && eval "$(docker_parse_build_args "build.$PROFILE.env")" [ -n "$PROFILE" -a -f "build.$PROFILE.env.local" ] && eval "$(docker_parse_build_args "build.$PROFILE.env.local")" } function docker_set_env_args() { [ -f .build.env ] && source ./.build.env # DEPRECATED [ -f build.env ] && source ./build.env [ -f build.env.local ] && source ./build.env.local [ -n "$PROFILE" -a -f ".build.$PROFILE.env" ] && source "./.build.$PROFILE.env" # DEPRECATED [ -n "$PROFILE" -a -f "build.$PROFILE.env" ] && source "./build.$PROFILE.env" [ -n "$PROFILE" -a -f "build.$PROFILE.env.local" ] && source "./build.$PROFILE.env.local" } 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() { # XXX obsolète: remplacé par le support natif des projets maven 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 local -a APPS DEFAULT_ORIGIN="$UPDATE_APPS_ORIGIN" [ -z "$DEFAULT_ORIGIN" ] && DEFAULT_ORIGIN=origin DEFAULT_BRANCH="$UPDATE_APPS_BRANCH" [ -z "$DEFAULT_BRANCH" -a "$PROFILE" == prod ] && DEFAULT_BRANCH=master [ -z "$DEFAULT_BRANCH" ] && DEFAULT_BRANCH=develop CLEAN= APPS=() [ -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 local before_build before_builds after_update after_updates 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 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}" DEVEL_SRCDIR="${var}_DEVEL_SRCDIR"; DEVEL_SRCDIR="${!DEVEL_SRCDIR}" if [ -z "$DEVEL_SRCDIR" -a -n "$URL" ]; then case "$TYPE" in composer) DEVEL_SRCDIR="${COMPOSER_DEVEL_SRCDIR:-$DEFAULT_DEVEL_SRCDIR}";; maven) DEVEL_SRCDIR="${MAVEN_DEVEL_SRCDIR:-$DEFAULT_DEVEL_SRCDIR}";; *) DEVEL_SRCDIR="$DEFAULT_DEVEL_SRCDIR";; esac DEVEL_SRCDIR="$DEVEL_SRCDIR/${URL##*/}" fi if [ -n "$BUILD_UPDATE_DEVEL" -a -n "$DEVEL_SRCDIR" ]; 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 elif [ -f "$DEST/pom.xml" ]; then TYPE=maven 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; } if [ "${BRANCH#^}" != "$BRANCH" ]; then git reset --hard "${BRANCH#^}" || { cd "$cwd"; eend; return 1; } else git reset --hard "$ORIGIN/$BRANCH" || { cd "$cwd"; eend; return 1; } fi cd "$cwd" else # reliquat mode devel? [ -d "$DEST" ] && rm -rf "$DEST" # clonage initial estep "Clonage $URL:$BRANCH --> $DEST" if [ "${BRANCH#^}" != "$BRANCH" ]; then git clone -o "$ORIGIN" "$URL" "$DEST" || { eend; return 1; } setx cwd=pwd cd "$DEST" git reset --hard "${BRANCH#^}" || { cd "$cwd"; eend; return 1; } cd "$cwd" else git clone -o "$ORIGIN" -b "$BRANCH" "$URL" "$DEST" || { eend; return 1; } fi 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 elif [ -f "$DEST/pom.xml" ]; then TYPE=maven 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 elif [ -f "$DEST/pom.xml" ]; then TYPE=maven else TYPE=none fi fi else # ne devrait pas se produire die "ni URL ni SRC ne sont définis" fi before_builds="${var}_BEFORE_BUILD" if is_defined "$before_builds"; then before_builds="$before_builds[@]"; before_builds=("${!before_builds}") else before_builds=() fi for before_build in "${before_builds[@]}"; do if [ "${before_build#/}" != "$before_build" ]; then # commande absolue, la lancer telle quelle etitle "$before_build" eval "$before_build" || { eend; eend; return 1; } eend elif [ "${before_build#./}" != "$before_build" ]; then # commande relative, la lancer telle quelle etitle "$before_build" eval "$before_build" || { eend; eend; return 1; } eend else # c'est une fonction update_apps_func_* etitle "$before_build" eval "update_apps_func_$before_build" || { eend; eend; return 1; } eend fi done estep "Type de dépôt: $TYPE" if [ "$TYPE" == composer ]; then local cvname cvvalue local composer_php composer_php_max composer_image composer_cmd composer_setup composer_action local -a composer_vars composer_args for cvname in composer_php composer_php_max composer_image composer_cmd composer_setup; do cvvalue="${var}_${PROFILE}_${cvname^^}"; cvvalue="${!cvvalue}" [ -n "$cvvalue" ] || { cvvalue="${var}_${cvname^^}"; cvvalue="${!cvvalue}"; } [ -n "$cvvalue" ] || { cvvalue="${PROFILE}_${cvname^^}"; cvvalue="${!cvvalue}"; } [ -n "$cvvalue" ] || { cvvalue="${cvname^^}"; cvvalue="${!cvvalue}"; } [ -n "$cvvalue" ] && composer_vars+=("${cvname^^}=$cvvalue") done 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="${PROFILE}_COMPOSER_ACTION"; composer_action="${!composer_action}"; } [ -n "$composer_action" ] || composer_action="${COMPOSER_ACTION:-install}" composer_args="${var}_${PROFILE}_COMPOSER_ARGS" is_defined "$composer_args" || composer_args="${var}_COMPOSER_ARGS" is_defined "$composer_args" || composer_args="${PROFILE}_COMPOSER_ARGS" is_defined "$composer_args" || composer_args="COMPOSER_ARGS" composer_args="${composer_args}[@]"; composer_args=("${!composer_args}") if [ -z "$BUILD_UPDATE_DEVEL" ]; then case "$composer_action" 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_vars[@]}" "$composer_action" "${composer_args[@]}" || { eend; return 1; } cd "$cwd" fi fi elif [ "$TYPE" == maven ]; then local cvname cvvalue local maven_java maven_image maven_cmd maven_setup maven_action local -a maven_vars maven_args for cvname in maven_java maven_image maven_cmd maven_setup; do cvvalue="${var}_${PROFILE}_${cvname^^}"; cvvalue="${!cvvalue}" [ -n "$cvvalue" ] || { cvvalue="${var}_${cvname^^}"; cvvalue="${!cvvalue}"; } [ -n "$cvvalue" ] || { cvvalue="${PROFILE}_${cvname^^}"; cvvalue="${!cvvalue}"; } [ -n "$cvvalue" ] || { cvvalue="${cvname^^}"; cvvalue="${!cvvalue}"; } [ -n "$cvvalue" ] && maven_vars+=("${cvname^^}=$cvvalue") done maven_action="${var}_${PROFILE}_MAVEN_ACTION"; maven_action="${!maven_action}" [ -n "$maven_action" ] || { maven_action="${var}_MAVEN_ACTION"; maven_action="${!maven_action}"; } [ -n "$maven_action" ] || { maven_action="${PROFILE}_MAVEN_ACTION"; maven_action="${!maven_action}"; } [ -n "$maven_action" ] || maven_action="${MAVEN_ACTION:-package}" maven_args="${var}_${PROFILE}_MAVEN_ARGS" is_defined "$maven_args" || maven_args="${var}_MAVEN_ARGS" is_defined "$maven_args" || maven_args="${PROFILE}_MAVEN_ARGS" is_defined "$maven_args" || maven_args="MAVEN_ARGS" maven_args="${maven_args}[@]"; maven_args=("${!maven_args}") if [ -z "$BUILD_UPDATE_DEVEL" ]; then case "$maven_action" in install|"clean package install"|cpi|i) maven_action="clean package install";; package|"clean package"|cp|p) maven_action="clean package";; package_only|po) maven_action="package";; none|nop) maven_action=;; *) ewarn "$maven_action: action non standard. elle sera utilisée en l'état";; esac if [ -n "$maven_action" ]; then estep "Compilation du projet maven" setx cwd=pwd cd "$DEST" auto_maven "${maven_vars[@]}" $maven_action "${maven_args[@]}" || { eend; return 1; } cd "$cwd" fi fi 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 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 build_merge_env() { # si les fichiers .shared_env et/ou .${MACHINE}_env existent, où $MACHINE # est la machine actuellement sélectionnée, alors les merger pour créer le # fichier .env # Si DOCKER_MACHINE n'est pas défini, prendre par défaut HOSTNAME local machine="${DOCKER_MACHINE_NAME:-$HOSTNAME}" local shared=.shared_env for_machine=".${machine}_env" [ -f "$shared" -o -f "$for_machine" ] || return 0 echo >.env "## fichier auto-généré. ne pas modifier ##" if [ -f "$shared" ]; then echo "## $shared ##" >>.env cat "$shared" >>.env fi if [ -f "$for_machine" ]; then echo "## $for_machine ##" >>.env cat "$for_machine" >>.env fi } 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} \ ${PULL:+--pull} \ "${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} \ ${PULL:+--pull} \ "${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 build_merge_env 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} \ ${PULL:+--pull} \ "${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" </etc/systemd/system/$container_name.service < $version)? 0: 1); ' -- "$COMPOSER_PHP_MAX" case $? in 0) use_image=1;; 1) use_image=;; *) ewarn "Erreur lors du lancement de PHP: est-il installé? Vous pouvez utiliser COMPOSER_PHP=any";; esac fi if [ -n "$use_image" -o "$COMPOSER_PHP" == none ]; then : # ok, on a déjà décidé elif [ -z "$COMPOSER_PHP" ]; then # pas de version minimum, tester simplement la valeur de COMPOSER_IMAGE [ "$COMPOSER_IMAGE" != none ] && 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" case $? in 0) use_image=1;; 1) use_image=;; *) ewarn "Erreur lors du lancement de PHP: est-il installé? Vous pouvez utiliser COMPOSER_PHP=any";; esac 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 default_local_maven() { # lancement direct if [ -n "$MAVEN_JAVA" ]; then urequire java select_java_exact "$MAVEN_JAVA" || die fi 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 "$@" ;; java) shift java "$@" ;; *) [ -n "$MAVEN_CMD" ] || MAVEN_CMD=mvn "$MAVEN_CMD" "$@" ;; esac } function default_docker_maven() { # 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"' ;; java) shift actualcmd='eval "su-exec \"$user\" java $args"' ;; *) actualcmd='eval "su-exec \"$user\" \"$maven\" $args"' ;; esac setx args=qvals "$@" local -a basecmd host_mapping setupscript runscript cmd basecmd=( -e user="$user" -e group="$group" -e projdir="$projdir" -e setup="$MAVEN_SETUP" -e maven="$MAVEN_CMD" -e args="$args" ${MAVEN_JAVA:+-e JAVA="$MAVEN_JAVA"} ) for host_mapping in "${HOST_MAPPINGS[@]}"; do basecmd+=(--add-host "$host_mapping") done basecmd+=(-v "$HOME:$HOME") if [ "${projdir#$HOME/}" == "$projdir" ]; then # si le répertoire de projet ne se trouve pas dans $HOME, le monter aussi basecmd+=(-v "$projdir:$projdir") fi setupscript='eval "$setup"' runscript=' echo "$user" >>/etc/passwd; user="${user%%:*}" echo "$group" >>/etc/group; group="${group%%:*}" [ -n "$maven" ] || maven=mvn cd "$projdir" '"$actualcmd" if [ -n "$MAVEN_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_maven_${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 $MAVEN_IMAGE" cmd=( "$DOCKER" create -it --name "${container_name}_ct" "${basecmd[@]}" "$MAVEN_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éé MAVEN_IMAGE="${container_name}_image" fi cmd=( "$DOCKER" run -it --rm "${basecmd[@]}" "$MAVEN_IMAGE" bash -c "$runscript" ) "${cmd[@]}" } function local_maven() { default_local_maven "$@"; } function docker_maven() { default_docker_maven "$@"; } function auto_maven() { # mappings d'hôtes if [ -z "$HOST_MAPPINGS" ]; then HOST_MAPPINGS="${PROFILE}_HOST_MAPPINGS[@]"; HOST_MAPPINGS=("${!HOST_MAPPINGS}") [ ${#HOST_MAPPINGS[*]} -gt 0 ] || HOST_MAPPINGS=("${DEFAULT_HOST_MAPPINGS[@]}") fi local MAVEN_JAVA= local MAVEN_IMAGE="$DEFAULT_MAVEN_IMAGE" local MAVEN_MACHINE=-u local MAVEN_CMD= local MAVEN_SETUP= [ -f .maven.conf ] && source ./.maven.conf # les premiers arguments peuvent servir à redéfinir les variables while [ $# -gt 0 ]; do case "$1" in MAVEN_*=*) setv "$1" shift ;; HOST_MAPPING=*) HOST_MAPPINGS+=("${1#HOST_MAPPING=}") shift ;; *) break;; esac done local version case "$1" in java*) version="${1#java}" [ -n "$version" ] && MAVEN_JAVA="$version" shift set -- java "$@" ;; esac local use_image if [ "$MAVEN_JAVA" == force -o "$MAVEN_JAVA" == any ]; then MAVEN_JAVA= use_image=1 elif [ "$MAVEN_JAVA" == none -o "$MAVEN_JAVA" == system ]; then MAVEN_JAVA= use_image= elif [ "$MAVEN_IMAGE" != none ]; then use_image=1 fi if [ -n "$use_image" ]; then [ "$MAVEN_IMAGE" != none ] || die "Vous devez spécifier l'image à utiliser pour maven" local PREVIOUS_DOCKER_MACHINE_NAME="$DOCKER_MACHINE_NAME" if [ -n "$MAVEN_MACHINE" -a "$DOCKER_MACHINE_NAME" != "$MAVEN_MACHINE" ]; then local -x DOCKER_TLS_VERIFY= DOCKER_HOST= DOCKER_CERT_PATH= DOCKER_MACHINE_NAME= if [ "$MAVEN_MACHINE" != -u ]; then local env setx env=docker-machine env "$MAVEN_MACHINE" 2>/dev/null || { eerror "$MAVEN_MACHINE: une erreur s'est produite lors de la sélection du noeud avec docker-machine" return 1 } eval "$env" fi fi docker_maven "$@" else local_maven "$@" 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 PROFILE= DM_ALIASES=() DM_PROFILES=() CLUSTERDIRS=() DEFAULT_DEVEL_SRCDIR="$HOME/wop" COMPOSER_DEVEL_SRCDIR="$HOME/wop/php" MAVEN_DEVEL_SRCDIR="$HOME/wop/sn" DEFAULT_COMPOSER_IMAGE=docker.univ-reunion.fr/image/phpbuilder DEFAULT_MAVEN_IMAGE=docker.univ-reunion.fr/image/javabuilder DEFAULT_HOST_MAPPINGS=() prod_HOST_MAPPINGS=( docker.univ-reunion.fr:10.82.70.154 repos.univ-reunion.fr:10.82.70.246 git.univ-reunion.fr:10.82.70.247 ) test_HOST_MAPPINGS=("${prod_HOST_MAPPINGS[@]}") set_defaults dk export PROFILE HOST_MAPPINGS=() chdir= CONFIG= DM_SET_MACHINE= SELECT_CONTAINER= SELECT_MACHINE= USE_STACK= FAKE= VARS=() FORCE= NO_CACHE= PULL= 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 -U,--pull PULL=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 !~ / 1) { filter = parts[1] tag = parts[2] } else { tag = "" } } NR == 1 { print; next } (filter == "" || $1 ~ filter) && (tag == "" || $2 ~ tag) && $2 !~ / 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 == "" || $2 == "") { 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=() while [ $# -gt 0 -a "$1" != -- ]; do if [[ "$1" == COMPOSER_*=* ]]; then args+=("$1"); shift elif [[ "$1" == HOST_MAPPING=* ]]; then args+=("$1"); shift else break fi done case "$cmd" in ci) args+=(install);; cu) args+=(update);; cr) args+=(rshell);; cs) args+=(shell);; esac while [ $# -gt 0 -a "$1" != -- ]; do args+=("$1"); shift done auto_composer "${args[@]}" ;; maven|mvn|mvr|mvs|java*) 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 if [[ "$1" == MAVEN_*=* ]]; then args+=("$1"); shift elif [[ "$1" == HOST_MAPPING=* ]]; then args+=("$1"); shift else break fi done case "$cmd" in mvr) args+=(rshell);; mvs) args+=(shell);; java*) args+=("$cmd");; esac while [ $# -gt 0 -a "$1" != -- ]; do args+=("$1"); shift done auto_maven "${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