From 0adfa413ae107f920a6b2f2a0aab290e809fd06e Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Sat, 14 Jan 2023 10:26:28 +0400 Subject: [PATCH] =?UTF-8?q?d=C3=A9but=20dkrun?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dkrun | 2884 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2884 insertions(+) create mode 100755 dkrun diff --git a/dkrun b/dkrun new file mode 100755 index 0000000..4624ed9 --- /dev/null +++ b/dkrun @@ -0,0 +1,2884 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +source "$(dirname "$0")/lib/ulib/ulib" || exit 1 +urequire DEFAULTS ptools xmlsupport + +SELF="$script" + +##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +## Aide + +function display_help() { + uecho "$scriptname: déployer/exécuter une application à base de containers + +USAGE + $scriptname [PROJDIR] [OPTIONS] [ENVVARS...] + +OPTIONS + -m, --machine MACHINE + Sélectionner la machine spécifiée avant de dérouler le script + + -n, --no-build + Ne pas reconstruire l'image avant de déployer/exécuter l'application + -b, --build + (Re)construire les images avant de déployer/exécuter l'application + -c, --config CONFIG + Lire un fichier de configuration au format dkbuild. Si cette option + n'est pas spécifiée, les fichiers ~/.dkbuild.env et /etc/dkbuild.env + sont testés dans l'ordre et automatiquement sélectionnés s'ils existent. + -e, --env VAR=VALUE + Spécifier la valeur d'une variable d'environnement. + note: les arguments ENVVARS de la forme VAR=VALUE décrits dans la + section USAGE permettent de spécifier des variables d'environnement + comme avec cette option + --arg ARG=VALUE + Spécifier la valeur d'un argument de build. + --no-cache + Ne pas utiliser le cache lors du build + -u, --pull-image + Essayer de récupérer une version plus récente de l'image source + -p, --push-image + Pousser les images construites vers la registry + + --profile PROFILE + -P, --prod + -T, --test + -E, --dtest + --devel + Spécifier le profil dans lequel construire l'image et/ou + déployer/exécuter l'application + + --start + -k, --stop + -r, --restart + Démarrer/déployer, Arrêter, ou Redémarrer/forcer un redéploiement de + l'application + +" +} + +function display_help_dkbuild() { + uecho "\ +OPTIONS + --help Aide générale +* --hdk Aide sur le format du fichier dkbuild + --href Référence sur les commandes utilisables dans un fichier dkbuild + +DKBUILD +======= + +Un fichier dkbuild est un script shell utilisé pour construire une ou plusieurs +images docker. + +A cause de l'implémentation utilisée pour les directives, le fichier doit être +parcouru (i.e exécuté) à de multiples reprises pour analyser les paramètres et +variables définis. il faut donc \"protéger\" les appels de scripts externes ou +de fonctions gourmandes avec les commandes 'run' et 'call' pour éviter que ces +commandes ne soient exécutées à plusieurs reprises. + +Quand un fichier dkbuild est exécuté, le répertoire courant est toujours le +répertoire qui contient le fichier + +## Distributions ############################################################### + +Une distribution s'entend au sens de la distribution linux utilisée comme base +pour l'image construite. L'idée est de pouvoir construire des images similaires +qui ne diffèrent que par la version de base du système d'exploitation + +Pour une même distribution, plusieurs versions d'une image peuvent être +construites. Une version est définie en ajoutant un préfixe à la distribution. + +La commande 'setdists' permet de lister explicitement les distributions valides +(et les versions associées le cas échéant). Si la distribution sélectionnée par +l'utilisateur n'est pas dans la liste fournie, le script s'arrête sans erreur. +La première distribution listée est spéciale: c'est la distribution la plus +récente, celle qui reçoit le tag :latest + +La commande 'dist' permet de tester si la distribution spécifiée en argument a +été sélectionnée par l'utilisateur. L'argument 'LATEST' permet de tester la +dernière distribution. + +La commande 'version' permet de tester si la version spécifiée en argument a été +sélectionnée par l'utilisateur. On part du principe qu'une distribution a déjà +été testée au préalable avec 'dist' + +Exemple: + setdists 3.0-d11 3.1-d11 d10 d9 + if dist d11; then + if version 3.0; then + ... + elif version 3.1; then + ... + fi + elif dist d10; then + ... + elif dist d9; then + ... + fi +Dans une même distribution, les versions doivent être ordonnées de la plus +ancienne à la plus récente. ici, la version 3.1 est listée après la version 3.0 +pour que l'image construite aie le tag :latest + +Note: 'setdists' ne doit être utilisé qu'une seule fois. Les invocations +suivantes sont ignorées. + +## Profils ##################################################################### + +Un profil correspond à l'environnement de destination de l'image: production, +test, développement. + +La commande 'setprofiles' permet de lister explicitement les profils valides. +Si le profil sélectionné par l'utilisateur n'est pas dans la liste fournie, le +script s'arrête avec une erreur. Le premier profil listé est spécial: c'est le +profil par défaut. + +La commande 'profile' permet de tester si le profil spécifié en argument a été +sélectionné par l'utilisateur. L'argument 'DEFAULT' permet de tester le profil +par défaut. + +Exemple: + setprofiles prod devel + if profile prod; then + ... + elif profile devel; then + ... + fi + +Si le build est indépendant de la distribution, ou si la distribution est +utilisée sans version, alors il est possible de préfixer le profil d'une +version. Exemple: + setprofiles v1-prod v2-prod + if profile prod; then + if version v1; then + ... + elif version v2; then + ... + fi + fi +Dans un même profil, les versions doivent être ordonnées de la plus ancienne à +la plus récente. + +Si les distributions sont utilisées avec des versions, alors c'est une erreur de +spécifier une version dans le profil + +Note: 'setprofiles' ne doit être utilisé qu'une seule fois. Les invocations +suivantes sont ignorées. + +## Versions #################################################################### + +Si la version de l'image à construire n'est liée ni à la distribution, ni au +profil, il est possible de la spécifier avec la commande 'setversion'. La +version spécifiée avec 'setversion' est utilisée par défaut pour toutes les +images dont la version n'est pas spécifiée dans la distribution ou le profil. + +La commande 'setversion' est évaluée en même temps que les commandes 'setenv', +ainsi il est possible d'utiliser la valeur d'une variable définie au préalable. + +## Environnement ############################################################### + +La commande 'machine' permet de tester si le build est fait sur la machine +spécifiée. Ce peut être la machine courante, ou la machine spécifiée avec +l'option --machine de la commande build + +Exemple: + if machine host{1,2,3}-prod; then + setprofiles prod + elif machine host{1,2,3}-test; then + setprofiles test + else + setprofiles devel test prod + fi + +Les variables sont de deux types: argument de build ou variable d'environnement + +La commande 'setenv' permet de définir une variable d'environnement. La commande +'setarg' permet de définir un argument de build. Ces valeurs sont alors +automatiquement utilisées à l'endroit approprié. + +Ces commandes acceptent une liste d'argument de la forme VAR[=VALUE] + +Si la valeur n'est pas spécifiée (e.g 'setarg DESTDIR'), alors la variable doit +être définie dans l'environnement courant. Si la variable n'est pas définie dans +l'environnement, alors le script s'arrête avec une erreur. + +Une fois qu'une variable est définie, il n'est plus possible de la modifier. Les +commandes alternatives 'resetenv' et 'resetarg' permettent de pallier cette +limitation. + +## Valeurs par défaut ########################################################## + +Toutes les commandes ont des arguments requis, mais aussi des arguments +facultatifs qui sont fournis sous la forme d'une liste d'éléments VAR=VALUE + +La commande 'default' permet de spécifier les valeurs par défaut de ces +arguments. Exemple: + if profile prod; then + default composer mode=production + elif profile devel; then + default composer mode=development + fi + composer install path/to/project + +Une fois qu'une valeur par défaut est définie, il n'est plus possible de la +modifier. La commande alternative 'resetdefault' permet de pallier cette +limitation. + +Définir des valeurs par défaut pour la commande 'docker' impacte la commande +'build' et toutes les commandes qui utilisent docker, comme 'composer' ou 'mvn' + +## Synchronisation de fichiers ################################################# + +On peut vouloir s'assurer de la présence de certains fichiers à certains +endroits. + +La commande 'checkout URL DESTDIR' permet de s'assurer qu'un checkout du dépôt +spécifié existe dans le répertoire DESTDIR. La branche ou le commit à utiliser, +la source en mode développement, etc. peuvent être spécifiés par des arguments +facultatifs. + +La commande 'copy SRC DEST' permet de s'assurer que SRC et DEST sont +synchronisés. Si possible, des liens physiques sont créés pour conserver +l'espace disque. Par défaut, les fichiers ne sont créés que s'ils n'existent +pas. + +## Support Composer, Maven, commandes génériques ############################### + +La commande 'composer install' permet d'installer les dépendances Composer d'un +projet + +La commande 'mvn package' permet de construire un projet Java. + +La commande 'run' permet de lancer une commande quelconque. La commande est +cherchée dans le PATH et exécutée avec son chemin complet. celà permet de lancer +des commandes comme 'mvn' ou 'composer' ou dont le nom correspond à une fonction +déjà définie. + +La commande 'call' permet de lancer une commande quelconque. La différence avec +'run' est que la commande est lancée telle quelle, sans modifications. Si une +fonction est définie, elle sera utilisée en priorité. + +Si elles sont utilisées sans argument, les commandes 'composer', 'mvn', 'run' et +'call' retournent vrai si la commande doit être exécutée dans le contexte +courant. Celà permet d'implémenter des traitements complexes. Ainsi le script +suivant: + run cmd1 + run cmd2 +est équivalent à: + if run; then + cmd1 + cmd2 + fi + +En phase d'analyse, ces commandes retournent faux, donc cmd1 et cmd2 ne seront +lancés qu'une seule fois lors de l'invocation de dkbuild. Cela signifie qu'il ne +faut pas utiliser des directives de définition de variables à l'intérieur. Par +exemple, le script suivant ne produit pas forcément l'effet escompté: + if run; then + setenv var=value + default + cmd args... + fi + +Bien entendu, si on veut être sûr que des commandes externes soient lancées, on +peut toujours utiliser 'run' à l'intérieur, e.g + if composer; then + run extcmd + func + composer args... + fi + +## Support Dockerfile et docker build ########################################## + +La commande 'dockerfile OUTPUT' permet de construire de façon incrémentale et +dynamique un fichier Dockerfile. Toutes les commandes d'un fichier dockerfile +traditionnelles sont reconnues et elles doivent être spécifiées après la +commande 'dockerfile'. + +La commande 'build' termine la construction du fichier Docker puis lance la +construction de l'image. +* arguments de build: les arguments de build définis dans le script sont passés + à docker pour la construction de l'image +* nom de l'image et tag: par défaut, la variable d'environnement IMAGE est + combinée le cas échéant avec DIST et VERSION pour déterminer les tags que + reçoit l'image construite. + +La commande 'cbuild' lance le build de toutes les images mentionnées dans les +fichiers 'docker-compose.yml' et 'docker-compose.PROFILE.yml' le cas échéant +* variables d'environnement: les arguments de build définis sont inscrits dans + un fichier .env qui est utilisé ensuite par docker compose + +Si aucune commande 'build' ou 'cbuild' ne figure dans le fichier, 'build' est +lancé automatiquement à la fin + +## Autres commandes ############################################################ + +Il est possible d'organiser le script dans plusieurs fichiers qui sont inclus +avec la commande 'include'. Fonctionnellement, cela se passe comme si le contenu +du fichier inclus était inséré dans le script principal, à ceci près que le +répertoire courant devient temporairement celui du fichier inclus. + +Exemple: + setdists d11 d10 d9 + if dist d9; then + include d9/dkbuild + elif dist d10; then + include d10/dkbuild + elif dist d11; then + include d11/dkbuild + fi + +La commande 'dkbuild' lance le build d'un répertoire destination. La différence +avec 'include' est que cela est fait dans un processus complètement différent, +comme si le build avait été lancé depuis la ligne de commande. + +Les commandes 'section', 'note', 'info', 'debug' permettent d'afficher des +messages de différents niveaux + +## Variables globales ########################################################## + +PROJDIR est le chemin absolu du projet, dans lequel se trouve le fichier dkbuild +initial + +La distribution actuellement sélectionnée se trouve dans la variable DIST. La +version actuellement sélectionnée se trouve dans la variable VERSION. Si la +commande 'setdists' n'est pas utilisée, alors ni DIST ni VERSION ne sont définis + +Le profil actuellement sélectionné se trouve dans la variable PROFILE. La +version actuellement sélectionnée se trouve dans la variable VERSION (si les +distributions sont utilisées sans le support de la version). Si la commande +'setprofiles' n'est pas utilisée, alors ni PROFILE ni VERSION ne sont définis + +IMAGE est le nom de l'image à construire. Si le nom de l'image contient le tag +(e.g IMAGE:TAG) alors le nom est utilisé tel quel. Sinon, DIST et VERSION sont +utilisés comme tags (i.e IMAGE:DIST et IMAGE:VERSION-DIST)" +} + +function display_help_reference() { + uecho "\ +OPTIONS + --help Aide générale + --hdk Aide sur le format du fichier dkbuild +* --href Référence sur les commandes utilisables dans un fichier dkbuild + +REFERENCE +========= + +## fonctions d'affichage + +USAGE: + section TITLE + note MESSAGE + info MESSAGE + debug MESSAGE + +## machine -- vérifier la machine courante + +## setdists -- spécifier les distributions valides + +## dist -- vérifier la distribution courante + +## setprofiles -- spécifier les profils valides + +## profile -- vérifier le profil courant + +## setversion -- spécifier la version par défaut + +USAGE: setversion VERSION + +Les paramètres optionnels sont +* from-file=FILE + prendre la version depuis le fichier spécifié. les formats pom.xml et + VERSION.txt sont supportés +* from-repo=REPO + calculer la version depuis le dépôt git spécifié +* from-glob=GLOB + calculer la version depuis le chemin spécifié +* extract=REGEXP + à partir de la valeur calculée par l'un des paramètres from-*, matcher + l'expression régulière au format AWK, et prendre comme version la valeur de + l'expression \$1 + Si from-glob est spécifié, la valeur par défaut de extract est calculée en + remplaçant '*' par '(.*)' +* add-prefix=PREFIX + Ajouter le préfixe spécifié à la version extraite +* add-suffix=SUFFIX + Ajouter le suffixe spécifié à la version extraite + +## version -- vérifier la version courante + +## setenv -- spécifier une variable d'environnement + +## resetenv -- spécifier une variable d'environnement + +## setarg -- spécifier une variable de build + +## resetarg -- spécifier une variable de build + +## default -- spécifier des arguments par défaut + +## resetdefault -- spécifier des arguments par défaut + +## checkout -- faire et vérifier checkout d'un dépôt git + +USAGE: checkout URL [DESTDIR] [PARAMS] + +Les paramètres optionnels sont +* checkout + mettre à jour dépôt. c'est la valeur par défaut. utiliser checkout= pour + désactiver la mise à jour du dépôt. utiliser checkout=devel pour synchroniser + depuis le répertoire de développement. +* origin=ORIGIN + spécifier l'origine. Par défaut, prendre 'origin' +* branch=BRANCH + spécifier la branche à utiliser dans l'origine spécifiée. La valeur par défaut + est 'master' si les profils ne sont pas utilisé. Si les profils sont utilisés, + la valeur par défaut est 'develop' pour les profils 'test' et 'devel', sinon + c'est 'master'. + Utiliser la syntaxe ^COMMIT pour ignorer l'origine et sélectionner un commit + en particulier. +* develdir=DIR + spécifier l'emplacement du répertoire de développement, utilisé avec + checkout=devel +* develtype=TYPE + spécifier le type de projet pour la synchronisation depuis le répertoire de + développement. certains projets, notamment les projets composer avec des + dépendances de type 'path', nécessitent une méthode de synchronisation + particulière. Si ce paramètre n'est pas spécifié, il est auto-détecté. Les + valeurs supportées sont: + * composer -- détecté de par la présence d'un fichier composer.json + * maven -- détecté de par la présence d'un fichier pom.xml + * none -- valeur par défaut: pas de type particulier + +## copy -- synchroniser des fichiers + +USAGE: copy SRC DEST [PARAMS] + +Si SRC est un fichier, alors DEST doit être un chemin vers le fichier +destination. Si SRC est un répertoire, alors DEST doit être un chemin vers le +répertoire destination. On peut forcer à considérer SRC et/ou DEST comme un +répertoire en les suffixant de '/' + +Par exemple: +* copy SRCDIR/ DESTDIR/ + échoue si SRCDIR et/ou DESTDIR sont des fichiers +* les commandes suivantes sont équivalentes: + copy SRC DESTDIR/ + copy SRC DESTDIR/SRCNAME + +Les paramètres optionnels sont +* copy + synchroniser les fichiers. c'est la valeur par défaut. utiliser copy= pour + désactiver la synchronisation des fichiers. +* overwrite + autoriser l'écrasement des fichiers destination, ce qui permet de rendre la + destination identique à la source. par défaut, un fichier destination est + laissé en place, ce qui permet d'avoir le cas échéant des fichiers locaux + différents de la source. +* gitignore=BASEDIR + maintenir le fichier .gitignore de BASEDIR, qui doit être un répertoire parent + de DESTDIR. Les fichiers synchronisés sont rajouté le cas échéant dans + .gitignore, sauf si le répertoire qui les contient est déjà exclu. + NB: Un fichier n'est considéré pour l'ajout dans .gitignore que s'il a été + copié au préalable. Ainsi, un fichier déjà existant dans la destination ne + sera pas ajouté dans le fichier .gitignore si overwrite= + +## genfile -- créer un fichier générique + +USAGE: genfile OUTPUT [INPUT] [PARAMS] + +Si le fichier INPUT est spécifié, il est utilisé pour créer le contenu initial +du fichier. Sinon, l'entrée standard *doit* être redirigée depuis un fichier, et +elle est lue pour générer le contenu initial du fichier à générer. + +Les paramètres optionnels sont +* context=DIR + générer le fichier dans le répertoire spécifié +* sed=SCRIPT + script sed à appliquer au fichier + +## dockerfile -- créer un fichier Dockerfile + +USAGE: dockerfile [OUTPUT [INPUT]] [PARAMS] + +Si le fichier INPUT est spécifié, il est utilisé pour créer le contenu initial +du fichier. Sinon, *si* l'entrée standard est redirigée depuis un fichier, elle +est lue pour générer le contenu initial du fichier. + +Les paramètres optionnels sont +* context=DIR + générer le fichier dans le répertoire spécifié +* sed=SCRIPT + script sed à appliquer au fichier initial, construit à partir de l'entrée + standard. Le fichier n'est plus modifié par la suite. + +## build -- construire une image avec docker + +USAGE: build [PARAMS] + +Les paramètres optionnels sont +* build + construire les images, c'est la valeur par défaut. utiliser build= pour + désactiver la construction. +* context=DIR + répertoire de contexte. cette valeur est en principe générée automatiquement + par la commande 'dockerfile' +* dockerfile=FILE + fichier de build à utiliser. cette valeur est en principe générée automatiquement + par la commande 'dockerfile' +* no-cache + ne pas utiliser le cache +* pull + forcer la mise à jour des images dépendantes +* host-mappings=MAPPINGS + définir des mappings d'hôte. si ce paramètre n'est pas spécifié, consulter + aussi la valeur par défaut 'docker host-mappings=' +* set-tag=TAGS... ou set-tags=TAGS... + spécifier des tags à rajouter au nom de l'image, séparés par un espace. cette + liste remplace celle calculée automatiquement. ce paramètre est ignoré pour + les noms d'images comportant un tag +* add-tag=TAGS... ou add-tags=TAGS... + spécifier des tags à rajouter à la liste calculée automatiquement, séparés par + un espace. ce paramètre est ignoré pour les noms d'images comportant un tag +* image=IMAGES... ou images=IMAGES... + liste de nom d'images, séparés par un espace. si les noms n'ont pas de tag, le + tag est construit à partir de DIST et VERSION sous la forme [VERSION-]DIST +* push + pousser les images vers le registry après les avoir construites + +## cbuild -- construire une image avec docker compose + +USAGE: cbuild [SERVICE] [PARAMS] + +Les paramètres optionnels sont +* files=FILES... + spécifier les fichiers docker-compose à utiliser. Par défaut, prendre + docker-compose.yml et docker-compose.PROFILE.yml +* project-name=PROJECT_NAME + spécifier le nom du projet +* no-cache + ne pas utiliser le cache +* pull + forcer la mise à jour des images dépendantes + +## include -- inclure un autre fichier dkbuild + +## dkbuild -- lancer un builder externe + +La commande dkbuild qui traite le script de build courant est lancée telle +quelle, avec la même syntaxe qu'en ligne de commande. L'intérêt de cette +commande est qu'on est assuré d'utiliser le même dkbuild que celui qui traite le +script de build courant. + +## composer -- gérer projet composer + +USAGE: composer DESTDIR [ACTION [PARAMS] [ARGS]] + +La destination est obligatoire. Sans arguments, cette commande retourne +simplement vrai + +Les actions valides sont +* install -- installer les dépendances. c'est l'action par défaut +* update -- mettre à jour les dépendances +* rshell -- lancer un shell root dans le répertoire du projet +* shell -- lancer un shell utilisateur dans le répertoire du projet +* none -- ne rien faire + +L'action est exécutée sur DESTDIR. Juste avant de lancer l'action, le répertoire +courant est modifié pour être DESTDIR, ce qui permet d'utiliser des chemins +relatifs le cas échéant. + +La commande 'rshell' lance un shell bash avec l'utilisateur root au lieu de +lancer la commande composer, ce qui permet de faire des opérations plus +complexes si le besoin s'en fait sentir. La commande alternative 'shell' lance +le shell avec le compte utilisateur. Ces commandes sont particulièrement utiles +si composer est paramétré pour être lancé dans un container + +Les paramètres optionnels sont +* args=ARGS... + arguments à rajouter à la commande composer. La valeur par défaut dépend du + profil: + * prod: --no-dev -o + * test: --no-dev -o + * autres profils: (pas d'arguments) +* php=VERSION + version de PHP en dessous de laquelle 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. +* php-max=VERSION + version de PHP à partir de laquelle image= est utilisé. 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. +* image=COMPOSER_IMAGE + image docker utilisée pour lancer composer. La valeur par défaut est la valeur + de la variable d'environnement \$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. +* machine=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. Spécifier 'current' pour ne pas modifier la valeur courante le cas + échéant +* host-mappings=MAPPINGS + définir des mappings d'hôte. si ce paramètre n'est pas spécifié, consulter + aussi la valeur par défaut 'docker host-mappings=' +* composer=PATH/TO/COMPOSER + 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 +* setup=CMDS... + liste de commandes à lancer pour configurer le container. 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é par exemple pour installer certains packages + nécessaire au projet. +* project-name=PROJECT_NAME + si setup= est défini, nommer l'image sur la base de ce nom. par défaut, le nom + est calculé automatiquement + +Si un fichier .composer.conf existe dans le répertoire du projet, il est sourcé +pour obtenir les valeurs par défaut des paramètres: +* COMPOSER_PHP -- valeur par défaut de php= +* COMPOSER_PHP_MAX -- valeur par défaut de php-max= +* COMPOSER_IMAGE -- valeur par défaut de image= +* COMPOSER_MACHINE -- valeur par défaut de machine= +* COMPOSER_CMD -- valeur par défaut de composer= +* COMPOSER_SETUP -- valeur par défaut de setup= + +## mvn -- construire projet maven + +USAGE: mvn DESTDIR [ACTION [PARAMS] [ARGS]] + +Le répertoire de destination est obligatoire. Sans arguments, cette commande +retourne simplement vrai. L'action par défaut est 'package' + +Les actions valides sont +* install -- lance mvn avec les commandes 'clean package install' +* package -- lance mvn avec les commandes 'clean package' +* package_only -- lance mvn avec uniquement la commande 'package' +* rshell -- lancer un shell root dans le répertoire du projet +* shell -- lancer un shell utilisateur dans le répertoire du projet +* java -- lancer java au lieu de lancer mvn. il est possible de spécifier la + version de java directement, e.g java7, java8, java11 + +L'action est exécutée sur DESTDIR. Juste avant de lancer l'action, le répertoire +courant est modifié pour être DESTDIR, ce qui permet d'utiliser des chemins +relatifs le cas échéant. + +La commande 'rshell' lance un shell bash avec l'utilisateur root au lieu de +lancer la commande mvn, ce qui permet de faire des opérations plus complexes si +le besoin s'en fait sentir. La commande alternative 'shell' lance le shell avec +le compte utilisateur. Ces commandes sont particulièrement utiles si mvn est +paramétré pour être lancé dans un container + +Les paramètres optionnels sont +* args=ARGS... + arguments à rajouter à la commande mvn +* java=VERSION + version de Java à sélectionner à l'intérieur de l'image docker + * Spécifier 'any' ou 'force' pour prendre la valeur par défaut + * Spécifier 'none' ou 'system' pour ne pas utiliser l'image docker +* image=MAVEN_IMAGE + image docker utilisée pour lancer mvn. La valeur par défaut est la valeur + de la variable d'environnement \$MAVEN_IMAGE + Spécifier 'none' pour lancer directement mvn sans passer par une image + docker, même si 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. +* machine=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. Spécifier 'current' pour ne pas modifier la valeur courante le cas + échéant +* host-mappings=MAPPINGS + définir des mappings d'hôte. si ce paramètre n'est pas spécifié, consulter + aussi la valeur par défaut 'docker host-mappings=' +* mvn=PATH/TO/MVN + chemin vers l'exécutable mvn. Par défaut, utiliser la commande trouvée dans le + PATH +* setup=CMDS... + liste de commandes à lancer pour configurer le container. 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é par exemple pour installer certains packages + nécessaire au projet. +* project-name=PROJECT_NAME + si setup= est défini, nommer l'image sur la base de ce nom. par défaut, le nom + est calculé automatiquement + +Si un fichier .maven.conf existe dans le répertoire du projet, il est sourcé +pour obtenir les valeurs par défaut des paramètres: +* MAVEN_JAVA -- valeur par défaut de java= +* MAVEN_IMAGE -- valeur par défaut de image= +* MAVEN_MACHINE -- valeur par défaut de machine= +* MAVEN_CMD -- valeur par défaut de mvn= +* MAVEN_SETUP -- valeur par défaut de setup= + +## run -- lancer des commandes + +## call -- lancer des commandes ou des fonctions +" +} + +##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +## shared + +declare -A PROTECTED_VARS=( + [PROTECTED_VARS]=1 + [SELF]=1 + [SHARED_LOCALS1]=1 + [SHARED_ARGS1]=1 + [SHARED_LOCALS2]=1 + [SHARED_ARGS2]=1 + [TFUNCTIONS]=1 + [FFUNCTIONS]=1 + [PROJDIR]=1 + [DKBUILD]=1 + [CONFIG]=1 + [MACHINE]=1 + [SETDISTS_DONE]=1 + [SETDISTS]=1 + [SETPROFILES_DONE]=1 + [SETPROFILES]=1 + [SETVERSION_DONE]=1 + [SETVERSION]=1 + [AUTOBUILD]=1 + [DISTS]=1 + [PROFILES]=1 + [_ENVIRON]=1 + [ENVIRON]=1 + [ARGS]=1 + [DEFAULTS]=1 +) + +SHARED_LOCALS1="local PROJDIR DKBUILD CONFIG" +SHARED_ARGS1=( + -j:,--projdir: PROJDIR= + -c:,--config: CONFIG= +) + +SHARED_LOCALS2="local DIST PROFILE; local -a TMPENVIRON TMPARGS" +SHARED_ARGS2=( + -d:,--dist: DIST= + -9,--d9 DIST=d9 + -0,--d10 DIST=d10 + -1,--d11 DIST=d11 + --r7,--rhel7 DIST=rhel7 + --r8,--rhel8 DIST=rhel8 + --ol7,--oracle7 DIST=ol7 + --ol8,--oracle8 DIST=ol8 + + -p:,--profile: PROFILE= + -P,--prod PROFILE=prod + -T,--test PROFILE=test + -E,--dtest PROFILE=dtest + --devel PROFILE=devel + + -e:,--env: '$TMPENVIRON+=("$value_")' + --arg: '$TMPARGS+=("$value_")' +) + +TFUNCTIONS=( + # dkbuild + section note info debug + setdists dist + setprofiles profile + setversion version + setenv resetenv + setarg resetarg + default resetdefault + checkout + copy + genfile + dockerfile + build + cbuild + # dockerfile + FROM + RUN + CMD + LABEL + MAINTAINER + EXPOSE + ENV + ADD + COPY + ENTRYPOINT + VOLUME + USER + WORKDIR + ARG + ONBUILD + STOPSIGNAL + HEALTHCHECK + SHELL +) +FFUNCTIONS=( + # dkbuild + composer + mvn + run + call + dkbuild +) + +function set_machine() { + local machine="$1" + if [ "$machine" == -u ]; then + # déselectionner la machine courante + local -x DOCKER_TLS_VERIFY= DOCKER_HOST= DOCKER_CERT_PATH= DOCKER_MACHINE_NAME= + machine= + fi + if [ -n "$machine" ]; then + if [ -f ~/etc/default/dk ]; then + machine="$( + CLUSTERDIRS=() + DM_ALIASES=() + source ~/etc/default/dk + for alias_machine in "${DM_ALIASES[@]}"; do + if [ "${alias_machine%%:*}" == "$machine" ]; then + echo "${alias_machine#*:}" + exit + fi + done + for clusterdir in "${CLUSTERDIRS[@]}"; do + if [ -f "$clusterdir/0config/configure.conf" ]; then + DM_ALIASES=() + source "$clusterdir/0config/configure.conf" + for alias_machine in "${DM_ALIASES[@]}"; do + if [ "${alias_machine%%:*}" == "$machine" ]; then + echo "${alias_machine#*:}" + exit + fi + done + fi + done + echo "$machine" + )" + fi + eval "$(docker-machine env "$machine" || echo false)" || die + else + machine="$DOCKER_MACHINE_NAME" + [ -n "$machine" ] || machine="${HOSTNAME%%.*}" + fi + MACHINE="$machine" +} + +function get_project_name() { + local project_name + setx project_name=basename -- "$(pwd)" + 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 + echo "$project_name" +} + +function get_container_name() { + local container_name="${1//[^a-zA-Z0-9_-]/}" + [ -n "$PROFILE" ] && container_name="${container_name}_$PROFILE" + echo "$container_name" +} + +function reset_functions() { + local func + for func in "${TFUNCTIONS[@]}"; do + eval "function $func() { : echo \"$func \$*\"; return 0; }" + done + for func in "${FFUNCTIONS[@]}"; do + eval "function $func() { : echo \"$func \$*\"; return 1; }" + done + function include() { + edebug "include $(qvals "$@")" + + local file="$1" + [ -d "$file" ] && file="$file/dkbuild" + [ -f "$file" ] || die "$file: fichier introuvable" + setx file=abspath "$file" + cd "$(dirname "$file")" + source "$file" + } + function machine() { + local machine version + for machine in "$@"; do + [ "$machine" == "$MACHINE" ] && return + done + return 1 + } +} + +function _runcmd() { + edebug "\$ $(qvals "$@")" + "$@" +} + +function ensure_projdir() { + if [ -z "$PROJDIR" ]; then + local found= + if [ ! -f dkbuild ]; then + # NB: on teste $PROJDIR != $scriptdir parce qu'il ne faut pas qu'on + # prenne le présent script comme un script de build... + PROJDIR="$(pwd)" + if [ "${PROJDIR#$HOME/}" != "$PROJDIR" ]; then + while [ "$PROJDIR" != "$HOME" ]; do + if [ -f "$PROJDIR/dkbuild" -a "$PROJDIR" != "$scriptdir" ]; then + found=1 + break + fi + setx PROJDIR=dirname "$PROJDIR" + done + else + while [ "$PROJDIR" != / ]; do + if [ -f "$PROJDIR/dkbuild" -a "$PROJDIR" != "$scriptdir" ]; then + found=1 + break + fi + setx PROJDIR=dirname "$PROJDIR" + done + fi + fi + if [ -n "$found" ]; then + enote "Sélection du répertoire de projet $(relpath "$PROJDIR")" + else + PROJDIR=. + fi + fi + + if [ -f "$PROJDIR" ]; then + DKBUILD="$PROJDIR" + setx PROJDIR=dirname "$PROJDIR" + else + DKBUILD="$PROJDIR/dkbuild" + fi + [ -d "$PROJDIR" ] || die "$PROJDIR: répertoire de projet introuvable" + + setx PROJDIR=abspath "$PROJDIR" + setx DKBUILD=abspath "$DKBUILD" + cd "$PROJDIR" || die + [ -f "$DKBUILD" ] || die "$(ppath "$DKBUILD"): fichier de build introuvable" + + if [ "$CONFIG" == none ]; then + edebug "no default config used" + elif [ -n "$CONFIG" ]; then + setx CONFIG=abspath "$CONFIG" + edebug "using config $CONFIG" + else + local config + for config in ~/.dkbuild.env /etc/dkbuild.env; do + if [ -f "$config" ]; then + CONFIG="$config" + edebug "using default config $CONFIG" + break + fi + done + fi +} + +function load_dkbuild() { + local dkbuildenv="$PROJDIR/$(basename "$DKBUILD").env" + cd "$PROJDIR" + if [ -n "$CONFIG" ]; then + edebug "loading $CONFIG" + source "$CONFIG" + fi + if [ -f "$dkbuildenv" ]; then + edebug "loading $dkbuildenv" + source "$dkbuildenv" + fi + edebug "loading $DKBUILD" + source "$DKBUILD" +} + +function load_environ() { + declare -g -A _ENVIRON + eval "$(declare -p -x | sed -r 's/^declare -x ([^=]+)=/_ENVIRON[\1]=/')" +} + +function from_glob() { + local var=value value + [[ "$1" != *=* ]] && { var="$1"; shift; } + + local path extract add_prefix add_suffix + while [ $# -gt 0 ]; do + case "$1" in + path=*) path="${1#path=}";; + extract=*) extract="${1#extract=}";; + add-prefix=*) add_prefix="${1#add-prefix=}";; + add-suffix=*) add_suffix="${1#add-suffix=}";; + *=*) ewarn "path: $1: argument ignoré";; + *) break;; + esac + shift + done + value="$(list_all . "$path" | sort -rn | head -1)" + [ -n "$extract" ] || extract="${path//\*/(.*)}" + if [ -n "$extract" ]; then + extract="${extract//\//\\/}" + value="$add_prefix$(awk -v version="$value" "BEGIN { + if (match(version, /$extract/, vs)) { print vs[1] } + else { print version } + }")$add_suffix" + fi + + local "$var"; upvar "$var" "$value" +} +function define_functions_env() { + function setversion() { + [ -n "$SETVERSION_DONE" ] && return + # sans argument, retourner 0 + [ $# -eq 0 ] && return + + local from_file from_repo from_glob extract add_prefix add_suffix + while [ $# -gt 0 ]; do + case "$1" in + from-file|file) from_file=.;; + from-file=*|file=*) + from_file="${1#from-}"; from_file="${from_file#file=}" + ;; + from-repo|repo) from_repo=.;; + from-repo=*|repo=*) + from_repo="${1#from-}"; from_repo="${from_repo#repo=}" + ;; + from-glob=*|glob=*) + from_glob="${1#from-}"; from_glob="${from_glob#glob=}" + ;; + extract=*) extract="${1#extract=}";; + add-prefix=*) add_prefix="${1#add-prefix=}";; + add-suffix=*) add_suffix="${1#add-suffix=}";; + *=*) ewarn "setversion: $1: argument ignoré";; + *) break;; + esac + shift + done + if [ -n "$from_file" ]; then + if [ -d "$from_file" ]; then + setx SETVERSION=pver --sw "$from_file" || die + elif [[ "$from_file" == *.xml ]]; then + setx SETVERSION=pver -E "$from_file" || die + else + setx SETVERSION=pver -F "$from_file" || die + fi + elif [ -n "$from_repo" ]; then + die "setversion from-repo: pas encore implémenté" #XXX + elif [ -n "$from_glob" ]; then + SETVERSION="$(list_all . "$from_glob" | sort -rn | head -1)" + [ -n "$extract" ] || extract="${from_glob//\*/(.*)}" + else + SETVERSION="$1" + fi + if [ -n "$extract" ]; then + extract="${extract//\//\\/}" + SETVERSION="$add_prefix$(awk -v version="$SETVERSION" "BEGIN { + if (match(version, /$extract/, vs)) { print vs[1] } + else { print version } + }")$add_suffix" + fi + SETVERSION_DONE=1 + } + function dist() { + local dist version latest + for dist in "$@"; do + if [ "$dist" == LATEST ]; then + latest=1 + continue + fi + parse_dist "$dist" dist version + [ "$dist" == "$DIST" ] || continue + [ -z "$version" -o "$version" == "$DVERSION" ] || continue + return 0 + done + if [ -n "$latest" ]; then + # trouver la dernière occurence de la première distribution + # mentionnée. en effet, les versions doivent être ordonnées de la + # plus ancienne à la plus récente. + local first_dist last_version + for dist in "${SETDISTS[@]}"; do + parse_dist "$dist" dist version + if [ -z "$first_dist" ]; then + first_dist="$dist" + last_version="$version" + elif [ "$dist" == "$first_dist" ]; then + last_version="$version" + fi + done + if [ "$first_dist" == "$DIST" ]; then + if [ -z "$last_version" -o "$last_version" == "$DVERSION" ]; then + return 0 + fi + fi + fi + return 1 + } + function version() { + local version + for version in "$@"; do + [ "$version" == "$VERSION" ] && return 0 + done + return 1 + } + function profile() { + local profile version + for profile in "$@"; do + [ "$profile" == DEFAULT ] && profile="${SETPROFILES[0]}" + parse_profile "$profile" profile version + [ "$profile" == "$PROFILE" ] || continue + [ -z "$version" -o "$version" == "$PVERSION" ] || continue + return 0 + done + return 1 + } + declare -g -A ENVIRON + function setenv() { + local name value + for name in "$@"; do + if [[ "$name" == *=* ]]; then + value="${name#*=}" + name="${name%%=*}" + else + value="${_ENVIRON[$name]-__UNDEFINED__}" + [ "$value" == __UNDEFINED__ ] && die "la variable d'environnement $name doit être définie" + fi + if [ "${ENVIRON[$name]-__UNDEFINED__}" == __UNDEFINED__ ]; then + # Ne spécifier la valeur que si elle n'a pas déjà été définie + _ENVIRON["$name"]="$value" + ENVIRON["$name"]="$value" + [ -z "${PROTECTED_VARS[$name]}" ] && _setv "export $name" "$value" + fi + done + } + function resetenv() { + local name value + for name in "$@"; do + if [[ "$name" == *=* ]]; then + value="${name#*=}" + name="${name%%=*}" + else + value="${_ENVIRON[$name]-__UNDEFINED__}" + [ "$value" == __UNDEFINED__ ] && die "la variable d'environnement $name doit être définie" + fi + _ENVIRON["$name"]="$value" + ENVIRON["$name"]="$value" + [ -z "${PROTECTED_VARS[$name]}" ] && _setv "export $name" "$value" + done + } + declare -g -A ARGS + function setarg() { + local name value + for name in "$@"; do + if [[ "$name" == *=* ]]; then + value="${name#*=}" + name="${name%%=*}" + else + value="${_ENVIRON[$name]-__UNDEFINED__}" + [ "$value" == __UNDEFINED__ ] && die "la variable d'environnement $name doit être définie" + fi + if [ "${ARGS[$name]-__UNDEFINED__}" == __UNDEFINED__ ]; then + # Ne spécifier la valeur que si elle n'a pas déjà été définie + ARGS["$name"]="$value" + fi + done + } + function resetarg() { + local name value + for name in "$@"; do + if [[ "$name" == *=* ]]; then + value="${name#*=}" + name="${name%%=*}" + else + value="${_ENVIRON[$name]-__UNDEFINED__}" + [ "$value" == __UNDEFINED__ ] && die "la variable d'environnement $name doit être définie" + fi + ARGS["$name"]="$value" + done + } + declare -g -A DEFAULTS + function default() { + local command="$1"; shift + local name value + for name in "$@"; do + if [[ "$name" == *=* ]]; then + value="${name#*=}" + name="${name%%=*}" + else + value=1 + fi + name="${command}_$name" + if [ "${DEFAULTS[$name]-__UNDEFINED__}" == __UNDEFINED__ ]; then + # Ne spécifier la valeur que si elle n'a pas déjà été définie + DEFAULTS["$name"]="$value" + fi + done + } + function resetdefault() { + local command="$1"; shift + local name value + for name in "$@"; do + if [[ "$name" == *=* ]]; then + value="${name#*=}" + name="${name%%=*}" + else + value=1 + fi + name="${command}_$name" + DEFAULTS["$name"]="$value" + done + } +} + +function parse_dist() { + local dist="$1" version + if [[ "$dist" == *-* ]]; then + version="${dist%-*}" + dist="${dist##*-}" + fi + local "${2:-dist}"; upvar "${2:-dist}" "$dist" + local "${3:-version}"; upvar "${3:-version}" "$version" +} + +function parse_profile() { + local profile="$1" version + if [[ "$profile" == *-* ]]; then + version="${profile%-*}" + profile="${profile##*-}" + fi + local "${2:-profile}"; upvar "${2:-profile}" "$profile" + local "${3:-version}"; upvar "${3:-version}" "$version" +} + +function resolve_dists_profiles() { + ## construire d'abord la liste des distributions et profils + edebug "Calcul de la liste des distributions et des profils" + reset_functions + SETDISTS_DONE= + SETDISTS=() + SETPROFILES_DONE= + SETPROFILES=() + SETVERSION_DONE= + SETVERSION= + AUTOBUILD=1 + + function setdists() { + [ -n "$SETDISTS_DONE" ] && return + SETDISTS=("$@") + SETDISTS_DONE=1 + } + function setprofiles() { + [ -n "$SETPROFILES_DONE" ] && return + SETPROFILES=("$@") + SETPROFILES_DONE=1 + } + function build() { + AUTOBUILD= + } + function cbuild() { + AUTOBUILD= + } + load_dkbuild + + ## ensuite vérifier si on est dans la bonne distribution + edebug "Calcul de la distribution courante" + reset_functions + DISTS=() + function setdists() { + local dist version found + # construire la liste des distributions à considérer + if [ -n "$DIST" ]; then + # on a spécifié une distribution en argument + for dist in "${SETDISTS[@]}"; do + if [ "$dist" == "$DIST" ]; then + # matcher avec la version éventuellement + found=1 + break + fi + parse_dist "$dist" + if [ "$dist" == "$DIST" ]; then + # ou matcher uniquement la distribution + found=1 + break + fi + done + # si aucune distribution ne correspond, arrêter le script + [ -n "$found" ] || exit 0 + # forcer à ne construire que cette distribution + DISTS=("$DIST") + else + DISTS=("${SETDISTS[@]}") + fi + } + load_dkbuild + + ## puis vérifier si on est dans le bon profil + edebug "Calcul du profil courant" + reset_functions + PROFILES=() + function setprofiles() { + local profile version found + # construire la liste des distributions à considérer + if [ -n "$PROFILE" ]; then + # on a spécifié une distribution en argument + for profile in "${SETPROFILES[@]}"; do + if [ "$profile" == "$PROFILE" ]; then + # matcher avec la version éventuellement + found=1 + break + fi + parse_profile "$profile" + if [ "$profile" == "$PROFILE" ]; then + # ou matcher uniquement la distribution + found=1 + break + fi + done + # si aucune distribution ne correspond, arrêter le script + [ -n "$found" ] || die "$PROFILE: profil invalide" + # forcer à ne construire que cette distribution + PROFILES=("$PROFILE") + else + PROFILES=("${SETPROFILES[@]}") + fi + } + load_dkbuild + + ## Si pas de distribution ou de profil, remplacer par valeur vide + if [ ${#DISTS[*]} -eq 0 ]; then + SETDISTS=("") + DISTS=("") + fi + if [ ${#PROFILES[*]} -eq 0 ]; then + SETPROFILES=("") + PROFILES=("") + fi + + ## puis calculer la version par défaut + edebug "Calcul de la version par défaut" + reset_functions + define_functions_env + load_dkbuild +} + +function foreach_dists_profiles() { + local each="$1" before="$2" after="$3" + + local version dist dversion profile pversion + local VERSION DIST DVERSION PROFILE PVERSION + local HAVE_VERSION LAST_VERSION + declare -A dones + + if [ -n "$before" ]; then + "$before" + fi + for dist in "${DISTS[@]}"; do + parse_dist "$dist" dist dversion + # y a-t-il une version dans cette distribution ou ce profil, et si oui, laquelle? + HAVE_VERSION= + LAST_VERSION= + for DIST in "${SETDISTS[@]}"; do + parse_dist "$DIST" DIST DVERSION + [ "$DIST" == "$dist" ] || continue + VERSION="$DVERSION" + for profile in "${PROFILES[@]}"; do + parse_profile "$profile" profile pversion + for PROFILE in "${SETPROFILES[@]}"; do + parse_profile "$PROFILE" PROFILE PVERSION + [ "$PROFILE" == "$profile" ] || continue + [ -n "$DVERSION" ] && PVERSION= || VERSION="$PVERSION" + [ -z "$VERSION" -a -n "$SETVERSION" ] && VERSION="$SETVERSION" + if [ -n "$VERSION" ]; then + HAVE_VERSION=1 + LAST_VERSION="$VERSION" + fi + done + done + done + + for DIST in "${SETDISTS[@]}"; do + parse_dist "$DIST" DIST DVERSION + [ "$DIST" == "$dist" ] || continue + [ -z "$dversion" -o "$DVERSION" == "$dversion" ] || continue + VERSION="$DVERSION" + + for profile in "${PROFILES[@]}"; do + parse_profile "$profile" profile pversion + for PROFILE in "${SETPROFILES[@]}"; do + parse_profile "$PROFILE" PROFILE PVERSION + [ "$PROFILE" == "$profile" ] || continue + if [ -z "$DVERSION" ]; then + [ -z "$pversion" -o "$PVERSION" == "$pversion" ] || continue + VERSION="$PVERSION" + else + PVERSION= + fi + + [ -n "${dones[$PVERSION-$PROFILE-$DVERSION-$DIST]}" ] && continue + dones["$PVERSION-$PROFILE-$DVERSION-$DIST"]=1 + + [ -z "$VERSION" -a -n "$SETVERSION" ] && VERSION="$SETVERSION" + + "$each" + done + done + done + done + if [ -n "$after" ]; then + "$after" + fi +} + +function define_functions_cmd() { + _IN_SECTION= + function section() { + [ -n "$_IN_SECTION" ] && eend + etitle "$*" + _IN_SECTION=1 + } + function note() { + enote "$*" + } + function info() { + estep "$*" + } + function debug() { + edebug "$*" + } + function checkout() { + edebug "checkout $(qvals "$@")" + + local url destdir + [[ "$1" != *=* ]] && { url="$1"; shift; } + [[ "$1" != *=* ]] && { destdir="$1"; shift; } + + local checkout="${DEFAULTS[checkout_checkout]-1}" + local origin="${DEFAULTS[checkout_origin]}" + local branch="${DEFAULTS[checkout_branch]}" + local develdir="${DEFAULTS[checkout_develdir]}" + local develtype="${DEFAULTS[checkout_develtype]}" + while [ $# -gt 0 ]; do + case "$1" in + checkout) checkout=1;; + checkout=*) checkout="${1#checkout=}";; + origin=*) origin="${1#origin=}";; + branch=*) branch="${1#branch=}";; + develdir=*) develdir="${1#develdir=}";; + develtype=*) develtype="${1#develtype=}";; + *) ewarn "checkout: $1: argument ignoré";; + esac + shift + done + + [ -n "$checkout" ] || return + [ -n "$url" -a -n "$destdir" ] || die "checkout: Vous devez spécifier l'url du dépôt et la destination" + [ -n "$origin" ] || origin=origin + if [ -z "$branch" ]; then + case "$PROFILE" in + test|devel) branch=develop;; + *) branch=master;; + esac + fi + + if [ "$checkout" == devel ]; then + # synchronisation depuis le répertoire de développement + [ -n "$develdir" ] || die "checkout: vous devez spécifier le répertoire de développement" + [ -d "$develdir" ] || die "checkout: répertoire de développement introuvable" + + die "Pas encore implémenté" #XXX + + elif [ -d "$destdir" -a -d "$destdir/.git" ]; then + # maj du dépôt + local cwd="$(pwd)" + + estep "checkout: maj du dépôt $url --> $destdir (origin=$origin, branch=$branch)" + cd "$destdir" + git fetch --all -p -f || die + if [ "${branch#^}" != "$branch" ]; then + git reset --hard "${branch#^}" || die + else + git reset --hard "$origin/$branch" || die + fi + cd "$cwd" + + else + # reliquat checkout=devel? + [ -d "$destdir" ] && rm -rf "$destdir" + + # clone + estep "checkout: clone du dépôt $url --> $destdir (origin=$origin, branch=$branch)" + if [ "${BRANCH#^}" != "$BRANCH" ]; then + local cwd="$(pwd)" + git clone -o "$origin" "$url" "$destdir" || die + cd "$destdir" + git reset --hard "${branch#^}" || die + cd "$cwd" + else + git clone -o "$origin" -b "$branch" "$url" "$destdir" || die + fi + fi + } + function copy() { + edebug "copy $(qvals "$@")" + + local src dest + [[ "$1" != *=* ]] && { src="$1"; shift; } + [[ "$1" != *=* ]] && { dest="$1"; shift; } + + local copy="${DEFAULTS[copy_copy]-1}" + local overwrite="${DEFAULTS[copy_overwrite]}" + local gitignore="${DEFAULTS[copy_gitignore]}" + while [ $# -gt 0 ]; do + case "$1" in + copy) copy=1;; + copy=*) copy="${1#copy=}";; + overwrite) overwrite=1;; + overwrite=*) overwrite="${1#overwrite=}";; + gitignore=*) gitignore="${1#gitignore=}";; + *) ewarn "copy: $1: argument ignoré";; + esac + shift + done + + [ -n "$copy" ] || return + [ -n "$src" -a -n "$dest" ] || die "copy: Vous devez spécifier la source et la destination de la copie" + [ -e "$src" ] || { + ewarn "copy: $src: fichier ou répertoire introuvables" + return 1 + } + + local srcdir destdir + if [ "${src%/}" != "$src" ]; then + [ -d "$src" ] || die "copy: $src: doit être un répertoire" + setx srcdir=abspath "$src" + src= + elif [ -d "$src" ]; then + setx srcdir=abspath "$src" + src= + else + setx src=abspath "$src" + fi + if [ "${dest%/}" != "$dest" ]; then + [ -f "$dest" ] && die "copy: $dest: doit être un répertoire" + setx destdir=abspath "$dest" + dest= + elif [ -d "$dest" ]; then + setx destdir=abspath "$dest" + dest= + elif [ -f "$dest" ]; then + [ -n "$srcdir" ] && die "copy: $dest: doit être un répertoire" + setx dest=abspath "$dest" + elif [ -n "$srcdir" ]; then + setx destdir=abspath "$dest" + dest= + else + setx dest=abspath "$dest" + fi + + local -a srcs dests + if [ -n "$srcdir" -a -n "$destdir" ]; then + # copie de répertoire à répertoire + local destpath="$(relpath "$destdir" "$PROJDIR")" + [ -n "$destpath" ] || destpath=. + estep "copy $(relpath "$srcdir" "$PROJDIR")/ --> $destpath/" + + array_from_lines srcs "$(find "$srcdir/" -type f -o -type l)" + for src in "${srcs[@]}"; do + dest="$destdir/${src#$srcdir/}" + if [ -n "$overwrite" -o ! -f "$dest" ]; then + mkdirof "$dest" || die + cp -dfl "$src" "$dest" || die + dests+=("$dest") + fi + done + + elif [ -n "$src" ]; then + # transformer copie de fichier à répertoire en copie de fichier à fichier + [ -n "$dest" ] || dest="$destdir/$(basename "$src")" + + if [ -n "$overwrite" -o ! -f "$dest" ]; then + # copie de fichier à fichier + estep "copy $(relpath "$src" "$PROJDIR") --> $(relpath "$dest" "$PROJDIR")" + mkdirof "$dest" || die + cp -fl "$src" "$dest" || die + + setx destdir=dirname "$dest" + dests+=("$dest") + fi + + else + # en réalité, on ne devrait jamais arriver ici + die "copy: impossible de copier un répertoire dans un fichier" + fi + + if [ -n "$gitignore" ]; then + setx gitignore=abspath "$gitignore" + [ -d "$gitignore" ] && gitignore="$gitignore/.gitignore" + + local basedir + setx basedir=dirname "$gitignore" + if [ "${destdir#$basedir/}" == "$destdir" -a "$destdir" != "$basedir" ]; then + ewarn "copy: gitignore ignoré parce que le répertoire n'est pas un parent de destdir" + else + [ -f "$gitignore" ] || { mkdir -p "$basedir"; touch "$gitignore"; } + + declare -A ignored_dirs + local ignored_dir + for dest in "${dests[@]}"; do + dest="/${dest#$basedir/}" + if grep -q "^$dest\$" "$gitignore"; then + ignored=1 + else + ignored= + setx ignored_dir=dirname "$dest" + while [ "$ignored_dir" != / ]; do + ignored="${ignored_dirs[$ignored_dir/]-compute}" + if [ "$ignored" == compute ]; then + grep -q "^$ignored_dir/\$" "$gitignore" && ignored=1 || ignored= + ignored_dirs["$ignored_dir/"]="$ignored" + fi + if [ -n "$ignored" ]; then + # un répertoire parent est déjà ignoré, on peut + # passer au fichier suivant + break + fi + setx ignored_dir=dirname "$ignored_dir" + done + fi + + if [ -z "$ignored" ]; then + # le fichier n'est pas ignoré, ni directement, ni via un + # répertoire parent. il faut donc l'ajouter à .gitignore + echo "$dest" >>"$gitignore" + fi + done + fi + fi + } + function genfile() { + edebug "genfile $(qvals "$@")" + + local output input + [[ "$1" != *=* ]] && { output="$1"; shift; } + [[ "$1" != *=* ]] && { input="$1"; shift; } + + local context="${DEFAULTS[genfile_context]}" + local sed="${DEFAULTS[genfile_sed]}" + while [ $# -gt 0 ]; do + case "$1" in + context=*) context="${1#context=}";; + sed=*) sed="${1#sed=}";; + *) ewarn "genfile: $1: argument ignoré";; + esac + shift + done + + [ -n "$output" ] || die "genfile: Vous devez spécifier le fichier en sortie" + if [ -n "$context" ]; then + mkdir -p "$context" || die + output="$context/$output" + fi + + if [ -n "$input" ]; then + cat "$input" >"$output" || die + elif ! tty -s; then + cat >"$output" + else + die "genfile: Vous devez spécifier une source pour le fichier $output" + fi + if [ -n "$sed" ]; then + sed -i "$sed" "$output" + fi + } + function dockerfile() { + edebug "dockerfile $(qvals "$@")" + + local input + [[ "$1" != *=* ]] && { DOCKERFILE="$1"; shift; } + [[ "$1" != *=* ]] && { input="$1"; shift; } + + local context="${DEFAULTS[dockerfile_context]}" + local sed="${DEFAULTS[dockerfile_sed]}" + while [ $# -gt 0 ]; do + case "$1" in + context=*) context="${1#context=}";; + sed=*) sed="${1#sed=}";; + *) ewarn "dockerfile: $1: argument ignoré";; + esac + shift + done + + [ -n "$DOCKERFILE" ] || DOCKERFILE=Dockerfile + DOCKERCONTEXT=. + if [ -n "$context" ]; then + mkdir -p "$context" || die + DOCKERCONTEXT="$context" + DOCKERFILE="$context/$DOCKERFILE" + fi + + setx DOCKERFILE=abspath "$DOCKERFILE" + if [ -n "$input" ]; then + cat "$input" >"$DOCKERFILE" || die + elif ! tty -s; then + cat >"$DOCKERFILE" || die + else + echo "# -*- coding: utf-8 mode: dockerfile -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8" >"$DOCKERFILE" + fi + if [ -n "$sed" ]; then + sed -i "$sed" "$DOCKERFILE" + fi + } + function add2dockerfile() { + edebug "$(qvals "$@")" + + [ -n "$DOCKERFILE" ] || return + echo "$*" >>"$DOCKERFILE" + } + function FROM() { add2dockerfile FROM "$@"; } + function RUN() { add2dockerfile RUN "$@"; } + function CMD() { add2dockerfile CMD "$@"; } + function LABEL() { add2dockerfile LABEL "$@"; } + function MAINTAINER() { add2dockerfile MAINTAINER "$@"; } + function EXPOSE() { add2dockerfile EXPOSE "$@"; } + function ENV() { add2dockerfile ENV "$@"; } + function ADD() { add2dockerfile ADD "$@"; } + function COPY() { add2dockerfile COPY "$@"; } + function ENTRYPOINT() { add2dockerfile ENTRYPOINT "$@"; } + function VOLUME() { add2dockerfile VOLUME "$@"; } + function USER() { add2dockerfile USER "$@"; } + function WORKDIR() { add2dockerfile WORKDIR "$@"; } + function ARG() { add2dockerfile ARG "$@"; } + function ONBUILD() { add2dockerfile ONBUILD "$@"; } + function STOPSIGNAL() { add2dockerfile STOPSIGNAL "$@"; } + function HEALTHCHECK() { add2dockerfile HEALTHCHECK "$@"; } + function SHELL() { add2dockerfile SHELL "$@"; } + function build() { + edebug "build $(qvals "$@")" + + local build="${DEFAULTS[build_build]-1}" + local no_cache="${DEFAULTS[build_no-cache]}" + local pull="${DEFAULTS[build_pull]}" + local host_mappings="${DEFAULTS[build_host-mappings]-__UNDEFINED__}" + [ "$host_mappings" == __UNDEFINED__ ] && host_mappings="${DEFAULTS[docker_host-mappings]}" + local set_tags="${DEFAULTS[build_set-tags]}" + local add_tags="${DEFAULTS[build_add-tags]}" + local images="${DEFAULTS[build_images]}" + local push="${DEFAULTS[build_push]}" + while [ $# -gt 0 ]; do + case "$1" in + build) build=1;; + build=*) build="${1#build=}";; + context=*) DOCKERCONTEXT="${1#context=}";; + dockerfile=*) DOCKERFILE="${1#dockerfile=}";; + no-cache) no_cache=1;; + no-cache=*) no_cache="${1#no-cache=}";; + pull) pull=1;; + pull=*) pull="${1#pull=}";; + host-mappings=*) host_mappings="${1#host-mappings=}";; + set-tag=*) set_tags="${1#set-tag=}";; + set-tags=*) set_tags="${1#set-tags=}";; + add-tag=*) add_tags="${1#add-tag=}";; + add-tags=*) add_tags="${1#add-tags=}";; + image=*) images="${1#image=}";; + images=*) images="${1#images=}";; + push) push=1;; + push=*) push="${1#push=}";; + *) ewarn "build: $1: argument ignoré";; + esac + shift + done + + estep "build options:" ${build:+build} ${no_cache:+no-cache} ${pull:+pull} ${push:+push} + + [ -n "$images" ] || images="$IMAGE" + eval "set_tags=($set_tags)" + eval "add_tags=($add_tags)" + eval "images=($images)" + local tag imagetag autotag=1 + local -a imagetags + if [ ${#set_tags[*]} -gt 0 ]; then + autotag= + for imagetag in "${images[@]}"; do + if [[ "$imagetag" == *:* ]]; then + # le tag est déjà spécifié + imagetags+=("$imagetag") + else + for tag in "${set_tags[@]}" "${add_tags[@]}"; do + imagetags+=("$imagetag:$tag") + done + fi + done + else + for imagetag in "${images[@]}"; do + if [[ "$imagetag" == *:* ]]; then + # le tag est déjà spécifié + autotag= + imagetags+=("$imagetag") + else + for tag in "${add_tags[@]}"; do + imagetags+=("$imagetag:$tag") + done + [ -n "$VERSION" ] && imagetags+=("$imagetag:$VERSION-$DIST") + [ -n "$DIST" -a -z "$HAVE_VERSION" ] && imagetags+=("$imagetag:$DIST") + fi + done + fi + if [ -n "$autotag" ]; then + if [ -n "$DIST" ]; then + if [ -z "$HAVE_VERSION" ]; then + dist LATEST && imagetags+=("$imagetag:latest") + elif [ "$VERSION" == "$LAST_VERSION" ]; then + imagetags+=("$imagetag:$DIST") + dist LATEST && imagetags+=("$imagetag:latest") + fi + elif [ -n "$PROFILE" ]; then + profile DEFAULT && imagetags+=("$imagetag:latest") + else + imagetags+=("$imagetag:latest") + fi + fi + + local avar + local -a args; args=( + ${no_cache:+--no-cache} + ${pull:+--pull} + ) + eval "host_mappings=($host_mappings)" + for host_mapping in "${host_mappings[@]}"; do + args+=(--add-host "$host_mapping") + done + for avar in "${!ARGS[@]}"; do + args+=(--build-arg "$avar=${ARGS[$avar]}") + done + for imagetag in "${imagetags[@]}"; do + args+=(-t "$imagetag") + estep "tag $imagetag" + done + + [ -n "$DOCKERCONTEXT" ] || DOCKERCONTEXT="${DEFAULTS[build_context]:-.}" + [ -n "$DOCKERFILE" ] || DOCKERFILE="${DEFAULTS[build_dockerfile]:-Dockerfile}" + if [ -n "$build" ]; then + etitle build + _runcmd docker build "${args[@]}" -f "$DOCKERFILE" "$DOCKERCONTEXT" || die + eend + fi + + if [ -n "$push" ]; then + etitle push + for imagetag in "${imagetags[@]}"; do + _runcmd docker push "$imagetag" || die + done + eend + fi + + DOCKERCONTEXT= + DOCKERFILE= + [ -n "$build" -o -n "$push" ] + } + function cbuild() { + edebug "cbuild $(qvals "$@")" + + local files="${DEFAULTS[cbuild_files]}" + local project_name="${DEFAULTS[cbuild_project-name]}" + local no_cache="${DEFAULTS[cbuild_no-cache]}" + local pull="${DEFAULTS[cbuild_pull]}" + while [ $# -gt 0 ]; do + case "$1" in + files=*) files="${1#files=}";; + project-name=*) project_name="${1#project-name=}";; + no-cache) no_cache=1;; + no-cache=*) no_cache="${1#no-cache=}";; + pull) pull=1;; + pull=*) pull="${1#pull=}";; + *=*) ewarn "cbuild: $1: argument ignoré";; + *) break;; + esac + shift + done + + if [ -n "$files" ]; then + eval "files=($files)" + else + files=(docker-compose.yml) + if [ -f "docker-compose.override.yml" ]; then + files+=("docker-compose.override.yml") + fi + if [ -n "$PROFILE" -a -f "docker-compose.$PROFILE.yml" ]; then + files+=("docker-compose.$PROFILE.yml") + fi + fi + [ -n "$project_name" ] || setx project_name=get_project_name + + estep "cbuild files:" ${files[@]} + estep "cbuild options:" project_name="$project_name" ${no_cache:+no-cache} ${pull:+pull} + + local file; local -a args + args=( + -p "$project_name" + ) + for file in "${files[@]}"; do + args+=(-f "$file") + done + local avar evar; local -a bargs + bargs=( + ${no_cache:+--no-cache} + ${pull:+--pull} + ) + for avar in "${!ARGS[@]}"; do + bargs+=(--build-arg "$avar=${ARGS[$avar]}") + done + + if [ -f .shared_env -o -f ".${MACHINE}_env" -o ${#ENVIRON[*]} -gt 0 ]; then + echo >.env "## fichier auto-généré. ne pas modifier ##" + fi + [ -f .shared_env ] && cat .shared_env >>.env + [ -f ".${MACHINE}_env" ] && cat ".${MACHINE}_env" >>.env + for evar in "${!ENVIRON[@]}"; do + echo_setv "$evar=${ENVIRON[$evar]}" >>.env + done + + _runcmd docker-compose "${args[@]}" build "${bargs[@]}" "$@" || die + } + function _local_composer() { + case "$action" in + rootshell|rshell|rootbash|rbash) + shift + estep "Lancement d'un shell root" + sudo bash "$@" + return $? + ;; + usershell|shell|userbash|bash) + shift + estep "Lancement d'un shell utilisateur" + bash "$@" + return $? + ;; + *) + estep "composer $action" + if [ -n "$composer" ]; then : + elif [ -x composer.phar ]; then composer=./composer.phar + elif [ -x /usr/bin/composer ]; then composer=/usr/bin/composer + else die "Impossible de trouver composer" + fi + "$composer" "$action" $args "$@" + esac + } + function _docker_composer() { + local user group projdir actualcmd + setx user=id -un; setx user=getent passwd "$user" + setx group=id -gn; setx group=getent group "$group" + setx projdir=pwd + case "$action" in + rootshell|rshell|rootbash|rbash) + action=rshell + shift + actualcmd='eval "bash $args"' + ;; + usershell|shell|userbash|bash) + action=shell + shift + actualcmd='eval "su-exec \"$user\" bash $args"' + ;; + *) + actualcmd='eval "su-exec \"$user\" \"$composer\" $args"' + args="$action${args:+ $args}" + ;; + esac + setx args=qvals $args "$@" + + local -a basecmd setupscript runscript cmd + basecmd=( + -e user="$user" + -e group="$group" + -e projdir="$projdir" + -e setup="$setup" + -e composer="$composer" + -e args="$args" + ) + eval "host_mappings=($host_mappings)" + 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 + 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 "$setup" ]; then + local project_name container_name ctid + + # lancement dans un container docker à préparer + [ -n "$project_name" ] || setx project_name=get_project_name + setx container_name=get_container_name "$project_name" + container_name="dkbuild_composer_${container_name}" + + # vérifier l'existence de l'image + setx ctid=docker image ls --format '{{.ID}}' "${container_name}_image" + + # créer le container le cas échéant + if [ -z "$ctid" ]; then + estep "Création du container $container_name à partir de l'image $image" + cmd=( + docker create -it --name "${container_name}_ct" + "${basecmd[@]}" + "$image" + bash -c "$setupscript" + ) + setx ctid="${cmd[@]}" && + docker container start -ai "$ctid" && + docker container commit "$ctid" "${container_name}_image" && + docker container rm "$ctid" || die + fi + + # prendre comme image le container créé + image="${container_name}_image" + fi + + case "$action" in + rshell) estep "Lancement d'un shell root (avec l'image $image)";; + shell) estep "Lancement d'un shell utilisateur (avec l'image $image)";; + *) estep "composer $action (avec l'image $image)";; + esac + cmd=( + docker run -it --rm + "${basecmd[@]}" + "$image" + bash -c "$runscript" + ) + "${cmd[@]}" + } + function composer() { + edebug "composer $(qvals "$@")" + [ $# -eq 0 ] && return 0 + + local action destdir + [[ "$1" != *=* ]] && { destdir="$1"; shift; } + [[ "$1" != *=* ]] && { action="$1"; shift; } + + [ -n "$destdir" ] || destdir=. + [ -d "$destdir" ] || die "composer: $destdir: répertoire introuvable" + local cwd="$(pwd)" + cd "$destdir" || die + + [ -n "$action" ] || action=install + [ "$action" == none ] && return + + local args + case "$PROFILE" in + prod|test) args="${DEFAULTS[composer_args]---no-dev -o}";; + *) args="${DEFAULTS[composer_args]}";; + esac + local php="${DEFAULTS[composer_php]}" + local php_max="${DEFAULTS[composer_php-max]}" + local image="${DEFAULTS[composer_image]}" + local machine="${DEFAULTS[composer_machine]}" + local host_mappings="${DEFAULTS[composer_host-mappings]-__UNDEFINED__}" + [ "$host_mappings" == __UNDEFINED__ ] && host_mappings="${DEFAULTS[docker_host-mappings]}" + local composer="${DEFAULTS[composer_composer]}" + local setup="${DEFAULTS[composer_setup]}" + local project_name="${DEFAULTS[composer_project-name]}" + if [ -f "$destdir/.composer.conf" ]; then + eval "$( + COMPOSER_PHP= + COMPOSER_PHP_MAX= + COMPOSER_IMAGE="$COMPOSER_IMAGE" + COMPOSER_MACHINE=-u + COMPOSER_CMD= + COMPOSER_SETUP= + source "$destdir/.composer.conf" + [ -z "$php" ] && echo_setv php="$COMPOSER_PHP" + [ -z "$php_max" ] && echo_setv php_max="$COMPOSER_PHP_MAX" + [ -z "$image" ] && echo_setv image="$COMPOSER_IMAGE" + [ -z "$machine" ] && echo_setv machine="$COMPOSER_MACHINE" + [ -z "$composer" ] && echo_setv composer="$COMPOSER_CMD" + [ -z "$setup" ] && echo_setv setup="$COMPOSER_SETUP" + )" + fi + + while [ $# -gt 0 ]; do + case "$1" in + args=*) args="${1#args=}";; + php=*) php="${1#php=}";; + php-max=*) php_max="${1#php-max=}";; + image=*) image="${1#image=}";; + machine=*) machine="${1#machine=}";; + host-mappings=*) host_mappings="${1#host-mappings=}";; + composer=*) composer="${1#composer=}";; + setup=*) setup="${1#setup=}";; + project-name=*) project_name="${1#project-name=}";; + *=*) ewarn "composer: $1: argument ignoré";; + *) break;; + esac + shift + done + + local use_image + if [ "$php" == force -o "$php" == any ]; then + use_image=1 + elif [ "$php" == none -o "$php" == system ]; then + php=none + use_image= + elif [ -n "$php_max" -a "$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); +' -- "$php_max" + case $? in + 0) use_image=1;; + 1) use_image=;; + *) ewarn "Erreur lors du lancement de PHP: est-il installé? Sinon, utilisez php=any";; + esac + fi + if [ -n "$use_image" -o "$php" == none ]; then + : # ok, on a déjà décidé + elif [ -z "$php" ]; then + # pas de version minimum, tester simplement la valeur de image + [ "$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); +' -- "$php" + case $? in + 0) use_image=1;; + 1) use_image=;; + *) ewarn "Erreur lors du lancement de PHP: est-il installé? Sinon, utilisez php=any";; + esac + fi + + if [ -n "$use_image" ]; then + local orig_machine + [ "$image" != none ] || die "Vous devez spécifier l'image à utiliser pour composer" + if [ -n "$machine" -a "$machine" != current -a "$DOCKER_MACHINE_NAME" != "$machine" ]; then + orig_machine="$DOCKER_MACHINE_NAME" + set_machine "$machine" + fi + _docker_composer "$@" + if [ -n "$_orig_machine" ]; then + set_machine "$orig_machine" + fi + else + _local_composer "$@" + fi + + # restaurer le répertoire courant + cd "$cwd" + } + function _local_mvn() { + if [ -n "$java" ]; then + urequire java + select_java_exact "$java" || die + export MVN_JAVA_VERSION="$java" + fi + + case "$action" in + rootshell|rshell|rootbash|rbash) + shift + estep "Lancement d'un shell root" + sudo bash "$@" + return $? + ;; + usershell|shell|userbash|bash) + shift + estep "Lancement d'un shell utilisateur" + bash "$@" + return $? + ;; + java) + shift + estep "java" + java "$@" + ;; + *) + estep "mvn $action" + + [ -n "$mvn" ] || setx mvn=which mvn 2>/dev/null + [ -n "$mvn" ] || die "mvn: commande introuvable" + + set -- "$action" $args "$@" + case "$1" in + install) set clean package install "${@:2}";; + package) set clean package "${@:2}";; + package_only) set package "${@:2}";; + esac + "$mvn" "$@" + esac + } + function _docker_mvn() { + local user group projdir actualcmd + setx user=id -un; setx user=getent passwd "$user" + setx group=id -gn; setx group=getent group "$group" + setx projdir=pwd + case "$action" in + rootshell|rshell|rootbash|rbash) + action=rshell + shift + actualcmd='eval "bash $args"' + ;; + usershell|shell|userbash|bash) + action=shell + shift + actualcmd='eval "su-exec \"$user\" bash $args"' + ;; + java) + shift + actualcmd='eval "su-exec \"$user\" java $args"' + ;; + *) + actualcmd='eval "su-exec \"$user\" \"$mvn\" $args"' + + set -- "$action" $args "$@" + case "$1" in + install) set clean package install "${@:2}";; + package) set clean package "${@:2}";; + package_only) set package "${@:2}";; + esac + args= + ;; + esac + setx args=qvals $args "$@" + + local -a basecmd setupscript runscript cmd + basecmd=( + -e user="$user" + -e group="$group" + -e projdir="$projdir" + -e setup="$setup" + -e mvn="$mvn" + -e args="$args" + ${java:+-e JAVA="$java"} + ) + eval "host_mappings=($host_mappings)" + 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 + cmd+=(-v "$projdir:$projdir") + fi + setupscript='eval "$setup"' + runscript=' +echo "$user" >>/etc/passwd; user="${user%%:*}" +echo "$group" >>/etc/group; group="${group%%:*}" + +[ -n "$mvn" ] || mvn=mvn + +cd "$projdir" +'"$actualcmd" + + if [ -n "$setup" ]; then + local project_name container_name ctid + + # lancement dans un container docker à préparer + [ -n "$project_name" ] || setx project_name=get_project_name + setx container_name=get_container_name "$project_name" + container_name="dkbuild_maven_${container_name}" + + # vérifier l'existence de l'image + setx ctid=docker image ls --format '{{.ID}}' "${container_name}_image" + + # créer le container le cas échéant + if [ -z "$ctid" ]; then + estep "Création du container $container_name à partir de l'image $image" + cmd=( + docker create -it --name "${container_name}_ct" + "${basecmd[@]}" + "$image" + bash -c "$setupscript" + ) + setx ctid="${cmd[@]}" && + docker container start -ai "$ctid" && + docker container commit "$ctid" "${container_name}_image" && + docker container rm "$ctid" || die + fi + + # prendre comme image le container créé + image="${container_name}_image" + fi + + case "$action" in + rshell) estep "Lancement d'un shell root (avec l'image $image)";; + shell) estep "Lancement d'un shell utilisateur (avec l'image $image)";; + java) estep "java (avec l'image $image)";; + *) estep "mvn $action (avec l'image $image)";; + esac + cmd=( + docker run -it --rm + "${basecmd[@]}" + "$image" + bash -c "$runscript" + ) + "${cmd[@]}" + } + function mvn() { + edebug "mvn $(qvals "$@")" + [ $# -eq 0 ] && return 0 + + local action destdir + [[ "$1" != *=* ]] && { destdir="$1"; shift; } + [[ "$1" != *=* ]] && { action="$1"; shift; } + + [ -n "$destdir" ] || destdir=. + [ -d "$destdir" ] || die "mvn: $destdir: répertoire introuvable" + local cwd="$(pwd)" + cd "$destdir" || die + + [ -n "$action" ] || action=package + [ "$action" == none ] && return + + local args="${DEFAULTS[mvn_args]}" + local java="${DEFAULTS[mvn_java]}" + local image="${DEFAULTS[mvn_image]}" + local machine="${DEFAULTS[mvn_machine]}" + local host_mappings="${DEFAULTS[mvn_host-mappings]-__UNDEFINED__}" + [ "$host_mappings" == __UNDEFINED__ ] && host_mappings="${DEFAULTS[docker_host-mappings]}" + local mvn="${DEFAULTS[mvn_mvn]}" + local setup="${DEFAULTS[mvn_setup]}" + local project_name="${DEFAULTS[mvn_project-name]}" + if [ -f "$destdir/.maven.conf" ]; then + eval "$( + MAVEN_JAVA= + MAVEN_IMAGE="$MAVEN_IMAGE" + MAVEN_MACHINE=-u + MAVEN_CMD= + MAVEN_SETUP= + source "$destdir/.maven.conf" + [ -z "$java" ] && echo_setv java="$MAVEN_JAVA" + [ -z "$image" ] && echo_setv image="$MAVEN_IMAGE" + [ -z "$machine" ] && echo_setv machine="$MAVEN_MACHINE" + [ -z "$mvn" ] && echo_setv mvn="$MAVEN_CMD" + [ -z "$setup" ] && echo_setv setup="$MAVEN_SETUP" + )" + fi + + while [ $# -gt 0 ]; do + case "$1" in + args=*) args="${1#args=}";; + java=*) java="${1#java=}";; + image=*) image="${1#image=}";; + machine=*) machine="${1#machine=}";; + host-mappings=*) host_mappings="${1#host-mappings=}";; + mvn=*) mvn="${1#mvn=}";; + setup=*) setup="${1#setup=}";; + project-name=*) project_name="${1#project-name=}";; + *=*) ewarn "mvn: $1: argument ignoré";; + *) break;; + esac + shift + done + + local version + case "$action" in + java*) + version="${action#java}" + [ -n "$version" ] && java="$version" + set java "${@:2}" + ;; + esac + + local use_image + if [ "$java" == force -o "$java" == any ]; then + java= + use_image=1 + elif [ "$java" == none -o "$java" == system ]; then + java= + use_image= + elif [ -n "$image" -a "$image" != none ]; then + use_image=1 + fi + + if [ -n "$use_image" ]; then + local orig_machine + [ "$image" != none ] || die "Vous devez spécifier l'image à utiliser pour mvn" + if [ -n "$machine" -a "$machine" != current -a "$DOCKER_MACHINE_NAME" != "$machine" ]; then + orig_machine="$DOCKER_MACHINE_NAME" + set_machine "$machine" + fi + _docker_mvn "$@" + if [ -n "$_orig_machine" ]; then + set_machine "$orig_machine" + fi + else + _local_mvn "$@" + fi + + # restaurer le répertoire courant + cd "$cwd" + } + function run() { + edebug "run $(qvals "$@")" + [ $# -eq 0 ] && return 0 + + local cmd="$1"; shift + if [ "${cmd#/}" != "$cmd" ]; then : + elif [ "${cmd#./}" != "$cmd" ]; then : + elif [ "${cmd#../}" != "$cmd" ]; then : + else + local abscmd + setx abscmd=which "$cmd" 2>/dev/null + [ -n "$abscmd" ] || die "run: $cmd: commande introuvable" + edebug "'$cmd' resolved as '$abscmd'" + cmd="$abscmd" + fi + "$cmd" "$@" || die + } + function call() { + edebug "call $(qvals "$@")" + [ $# -eq 0 ] && return 0 + + "$@" + } + function dkbuild() { + edebug "dkbuild $(qvals "$@")" + + "$SELF" "$@" || die + } +} + +##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +## init + +function templates_action() { + declare -A TEMPLATES + [ -f "$TEMPLATEDIR/templates.conf" ] && source "$TEMPLATEDIR/templates.conf" + + etitle "Templates valides" + local -a templates + setx -a templates=list_dirs "$TEMPLATEDIR" + for template in "${templates[@]}"; do + [ "$template" == default ] && continue + desc="${TEMPLATES[$template]}" + estep "$template${desc:+ - "$desc"}" + done + eend +} + +##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +## init + +function init_action() { + eval "$SHARED_LOCALS1" + local template; local -a defaultvars addvars + local -a args; args=( + "${SHARED_ARGS1[@]}" + -t:,--template: template= + -v:,--var: '$addvars+=("$value_")' + -n:,--name: '$addvars+=(name="$value_")' + -g:,--group: '$addvars+=(group="$value_")' + ) + parse_args "$@"; set -- "${args[@]}" + + defaultvars=(name=name group=group) + [ $# -gt 0 ] && { [ -n "$1" ] && PROJDIR="$1"; shift; } + [ $# -gt 0 ] && { [ -n "$1" ] && addvars+=(name="$1"); shift; } + [ $# -gt 0 ] && { [ -n "$1" ] && addvars+=(group="$1"); shift; } + + declare -A vars + local name value + for name in "${defaultvars[@]}" "${addvars[@]}"; do + if [[ "$name" == *=* ]]; then + value="${name#*=}" + name="${name%%=*}" + else + value= + fi + name="${name^^}" + vars[$name]="$value" + done + + [ -n "$template" ] || template=default + [ -d "$TEMPLATEDIR/$template" ] || die "$template: template introuvable" + + [ -n "$PROJDIR" ] || PROJDIR=. + setx PROJDIR=abspath "$PROJDIR" + if [ ! -d "$PROJDIR" ]; then + ask_yesno "Voulez-vous créer le nouveau projet $(ppath "$PROJDIR")?" O || die + fi + mkdir -p "$PROJDIR" + + local sedscript + for name in "${!vars[@]}"; do + value="${vars[$name]}" + [ -n "$sedscript" ] && sedscript="$sedscript; " + sedscript="${sedscript}s/${name//\//\\\/}/${value//\//\\\/}/g" + done + + local src mode dest link + template="$TEMPLATEDIR/$template" + enote "Initialisation de $(ppath "$PROJDIR")" + find "$template/" -type f -o -type l | while read src; do + dest="$PROJDIR/${src#$template/}" + if [ -L "$src" ]; then + setx link=readlink "$src" + if [ -L "$dest" ]; then + if [ "$(readlink "$dest")" != "$link" ]; then + estep "${src#$template/} [link, updated]" + ln -sf "$link" "$dest" + else + edebug "${src#$template/} [exists, ignored]" + fi + elif [ -e "$dest" ]; then + estepe "${src#$template/} [destination is not a link]" + else + estep "${src#$template/} [link]" + ln -s "$link" "$dest" + fi + elif [ -f "$dest" ]; then + edebug "${src#$template/} [exists, ignored]" + else + estep "${src#$template/}" + mkdirof "$dest" + sed <"$src" >"$dest" "$sedscript" + setx mode=stat -c %a "$src" + chmod "$mode" "$dest" + fi + done +} + +##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +## build + +function build_action() { + eval "$SHARED_LOCALS1; $SHARED_LOCALS2" + local action=build + local machine + local clone_src_only update_src_only update_src sync_src + local build no_cache pull_image + local push_image + local -a args; args=( + "${SHARED_ARGS1[@]}" "${SHARED_ARGS2[@]}" + -m:,--machine: machine= + --clone-src-only clone_src_only=1 + --update-src-only update_src_only=1 + --update-src update_src=1 + --no-update-src update_src=no + -w,--update-devel-src update_src=devel + -s,--sync-src sync_src=1 + --no-sync-src sync_src=no + -b,--build build=1 + --no-cache no_cache=1 + -u,--pull-image pull_image=1 + -p,--push-image push_image=1 + ) + parse_args "$@"; set -- "${args[@]}" + + if [ -n "$clone_src_only" ]; then + action=clone_src + elif [ -n "$update_src_only" ]; then + action=update_src + else + action=build + [ -n "$update_src" ] || update_src=1 + [ "$update_src" == no ] && update_src= + if [ -z "$sync_src" -a -z "$build" -a -z "$push_image" ]; then + sync_src=1 + build=1 + fi + [ "$sync_src" == no ] && sync_src= + fi + + edebug "build_action" + set_machine "$machine" + + ensure_projdir + resolve_dists_profiles + setenv "${TMPENVIRON[@]}" + setarg "${TMPARGS[@]}" + setarg "$@" + + case "$action" in + clone_src) + die "Pas encore implémenté" #XXX + ;; + update_src) + die "Pas encore implémenté" #XXX + ;; + build) + default checkout checkout="$update_src" + default copy copy="$sync_src" + default build build="$build" ${no_cache:+no-cache} ${pull_image:+pull} ${push_image:+push} + [ $# -gt 0 ] && default build "$@" + define_functions_cmd + + foreach_dists_profiles _build_each _build_before _build_after + ;; + esac +} + +function _build_before() { + PREV_DIST= + PREV_PROFILE= +} +function _build_each() { + if [ "$PROFILE-$PVERSION" != "$PREV_PROFILE" ]; then + [ -n "$PREV_PROFILE" ] && eend + fi + if [ -n "$DIST" -a "$DIST-$DVERSION" != "$PREV_DIST" ]; then + [ -n "$PREV_DIST" ] && eend + PREV_DIST="$DIST-$DVERSION" + etitle "Distribution ${DVERSION:+$DVERSION-}$DIST" + fi + if [ -n "$PROFILE" -a "$PROFILE-$PVERSION" != "$PREV_PROFILE" ]; then + PREV_PROFILE="$PROFILE-$PVERSION" + etitle "Profil ${PVERSION:+$PVERSION-}$PROFILE" + fi + + load_dkbuild + if [ -n "$AUTOBUILD" ]; then + if [ -f docker-compose.yml ]; then + cbuild + else + build + fi + fi +} +function _build_after() { + if [ -n "$PREV_PROFILE" ]; then eend; fi + if [ -n "$PREV_DIST" ]; then eend; fi +} + +##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +## clean + +function _clean_git_clean() { + LANG=C git clean -d $opt "$@" | + grep -vE '^(Would skip|Skipping) ' | + sed -r 's/^Would remove //' +} +function _clean_git_status() { + git status --porcelain --ignored | + grep '^!! ' | + sed 's/^...//' +} + +function clean_action() { + eval "$SHARED_LOCALS1" + local opt=-X all= + local -a args; args=( + "${SHARED_ARGS1[@]}" + -X,--ignored opt=-X + -x,--untracked opt=-x + -a,--all all=1 + ) + parse_args "$@"; set -- "${args[@]}" + + [ -n "$all" ] && opt=-x + + edebug "clean_action" + ensure_projdir + + local cleans + setx cleans=_clean_git_clean -n "$@" + if [ -n "$cleans" ]; then + if check_interaction -c; then + einfo "via git clean" + eecho "$cleans" + ask_yesno "Voulez-vous supprimer ces fichiers?" O || die + fi + + _clean_git_clean -f "$@" || die + fi + + if [ -n "$all" ]; then + setx cleans=_clean_git_status + if [ -n "$cleans" ]; then + if check_interaction -c; then + einfo "via git status" + eecho "$cleans" + ask_yesno "Voulez-vous supprimer ces fichiers supplémentaires?" O || die + fi + + sed 's/^/Removing /' <<<"$cleans" + eval "cleans=($cleans);"' rm -rf "${cleans[@]}"' || die + fi + fi +} + +##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +## composer + +function composer_action() { + local -a args; args=( + ) + parse_args "$@"; set -- "${args[@]}" + + [ $# -gt 0 ] || set . + + edebug "composer_action" + + define_functions_cmd + composer "$@" +} + +##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +## mvn + +function mvn_action() { + local -a args; args=( + ) + parse_args "$@"; set -- "${args[@]}" + + [ $# -gt 0 ] || set . + + edebug "mvn_action" + + define_functions_cmd + mvn "$@" +} + +##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +## dump + +function dump_action() { + eval "$SHARED_LOCALS1; $SHARED_LOCALS2" + local machine + local -a args; args=( + "${SHARED_ARGS1[@]}" "${SHARED_ARGS2[@]}" + -m:,--machine: machine= + ) + parse_args "$@"; set -- "${args[@]}" + + edebug "dump_action" + set_machine "$machine" + + ensure_projdir + resolve_dists_profiles + setenv "${TMPENVIRON[@]}" + setarg "${TMPARGS[@]}" + setarg "$@" + + foreach_dists_profiles _dump_each _dump_before _dump_after +} + +function _dump_before() { + PREV_DIST= + PREV_PROFILE= +} +function _dump_each() { + if [ "$PROFILE-$PVERSION" != "$PREV_PROFILE" ]; then + [ -n "$PREV_PROFILE" ] && eend + fi + if [ -n "$DIST" -a "$DIST-$DVERSION" != "$PREV_DIST" ]; then + [ -n "$PREV_DIST" ] && eend + PREV_DIST="$DIST-$DVERSION" + etitle "Distribution ${DVERSION:+$DVERSION-}$DIST" + fi + if [ -n "$PROFILE" -a "$PROFILE-$PVERSION" != "$PREV_PROFILE" ]; then + PREV_PROFILE="$PROFILE-$PVERSION" + etitle "Profil ${PVERSION:+$PVERSION-}$PROFILE" + fi + + load_dkbuild + + etitle "Variables d'environnement" + for name in "${!ENVIRON[@]}"; do + estep "$name=${ENVIRON[$name]}" + done + eend + + etitle "Variables de build" + for name in "${!ARGS[@]}"; do + estep "$name=${ARGS[$name]}" + done + eend + + etitle "Valeurs par défaut" + for name in "${!DEFAULTS[@]}"; do + estep "$name=${DEFAULTS[$name]}" + done + eend +} +function _dump_after() { + if [ -n "$PREV_PROFILE" ]; then eend; fi + if [ -n "$PREV_DIST" ]; then eend; fi +} + +##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +## + +# faire une copie de l'environnement original +load_environ + +# si aucune action n'est spécifiée, il faut inférer build pour que ses options +# soient reconnues +args=() +while [ $# -gt 0 ]; do + case "$1" in + --help) args+=("$1"); shift; continue;; + --hdk|--help-dkbuild) args+=("$1"); shift; continue;; + --href|--help-reference) args+=("$1"); shift; continue;; + -*|*=*) # option quelconque: inférer build + args+=(build) + break + ;; + *) # argument quelconque: on s'arrête ici + break + ;; + esac +done +set -- "${args[@]}" "$@" + +args=(+ + --help '$exit_with display_help' + --hdk,--help-dkbuild '$exit_with display_help_dkbuild' + --href,--help-reference '$exit_with display_help_reference' +) +parse_args "$@"; set -- "${args[@]}" + +# aliases +case "$1" in +ci) set composer "$2" install "${@:3}";; +cu) set composer "$2" update "${@:3}";; +cr) set composer "$2" rshell "${@:3}";; +cs) set composer "$2" shell "${@:3}";; +mvr) set mvn "$2" rshell "${@:3}";; +mvs) set mvn "$2" shell "${@:3}";; +java) set mvn "$2" java "${@:3}";; +esac + +# actions +action="${1:-build}"; shift +case "$action" in +templates|t) templates_action "$@";; +init|i|0) init_action "$@";; +build|b) build_action "$@";; +clean|k) clean_action "$@";; +composer|c) composer_action "$@";; +maven|mvn|m) mvn_action "$@";; +dump|d) dump_action "$@";; +*) die "$action: action invalide";; +esac